mirror of
https://github.com/langgenius/dify.git
synced 2026-04-15 09:57:03 +08:00
refactor(web): remove highPriority modal stacking (#35132)
This commit is contained in:
parent
79c1473378
commit
21ab9b9d8c
@ -135,19 +135,6 @@ describe('Modal', () => {
|
||||
expect(container).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply highPriority z-index when highPriority is true', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<Modal isShow={true} title="Test Modal" highPriority={true}>
|
||||
<div>Content</div>
|
||||
</Modal>,
|
||||
)
|
||||
})
|
||||
|
||||
const dialog = document.querySelector('.z-1100')
|
||||
expect(dialog).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply overlayOpacity background when overlayOpacity is true', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
|
||||
@ -9,7 +9,7 @@ const meta = {
|
||||
layout: 'fullscreen',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Lightweight modal wrapper with optional header/description, close icon, and high-priority stacking for dropdown overlays.',
|
||||
component: 'Lightweight modal wrapper with optional header/description and close icon.',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -43,10 +43,6 @@ const meta = {
|
||||
control: 'boolean',
|
||||
description: 'Allows content to overflow the modal panel.',
|
||||
},
|
||||
highPriority: {
|
||||
control: 'boolean',
|
||||
description: 'Lifts the modal above other high z-index elements like dropdowns.',
|
||||
},
|
||||
onClose: {
|
||||
control: false,
|
||||
description: 'Callback invoked when the modal requests to close.',
|
||||
@ -115,18 +111,17 @@ export const Default: Story = {
|
||||
render: args => <ModalDemo {...args} />,
|
||||
}
|
||||
|
||||
export const HighPriorityOverflow: Story = {
|
||||
export const OverflowVisible: Story = {
|
||||
render: args => <ModalDemo {...args} />,
|
||||
args: {
|
||||
highPriority: true,
|
||||
overflowVisible: true,
|
||||
description: 'Demonstrates the modal configured to sit above dropdowns while letting the body content overflow.',
|
||||
description: 'Demonstrates the modal configured to let the body content overflow.',
|
||||
className: 'max-w-[540px]',
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Shows the modal with `highPriority` and `overflowVisible` enabled, useful when nested within complex surfaces.',
|
||||
story: 'Shows the modal with `overflowVisible` enabled for content that needs to escape the panel bounds.',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -20,7 +20,6 @@ type IModal = {
|
||||
children?: React.ReactNode
|
||||
closable?: boolean
|
||||
overflowVisible?: boolean
|
||||
highPriority?: boolean // For modals that need to appear above dropdowns
|
||||
overlayOpacity?: boolean // For semi-transparent overlay instead of default
|
||||
clickOutsideNotClose?: boolean // Prevent closing when clicking outside modal
|
||||
}
|
||||
@ -36,13 +35,12 @@ export default function Modal({
|
||||
children,
|
||||
closable = false,
|
||||
overflowVisible = false,
|
||||
highPriority = false,
|
||||
overlayOpacity = false,
|
||||
clickOutsideNotClose = false,
|
||||
}: IModal) {
|
||||
return (
|
||||
<Transition appear show={isShow} as={Fragment}>
|
||||
<Dialog as="div" className={cn('relative', highPriority ? 'z-1100' : 'z-60', wrapperClassName)} onClose={clickOutsideNotClose ? noop : onClose}>
|
||||
<Dialog as="div" className={cn('relative z-60', wrapperClassName)} onClose={clickOutsideNotClose ? noop : onClose}>
|
||||
<TransitionChild>
|
||||
<div className={cn('fixed inset-0', overlayOpacity ? 'bg-workflow-canvas-canvas-overlay' : 'bg-background-overlay', 'duration-300 ease-in data-closed:opacity-0', 'data-enter:opacity-100', 'data-leave:opacity-0')} />
|
||||
</TransitionChild>
|
||||
@ -59,19 +57,19 @@ export default function Modal({
|
||||
{!!title && (
|
||||
<DialogTitle
|
||||
as="h3"
|
||||
className="text-text-primary title-2xl-semi-bold"
|
||||
className="title-2xl-semi-bold text-text-primary"
|
||||
>
|
||||
{title}
|
||||
</DialogTitle>
|
||||
)}
|
||||
{!!description && (
|
||||
<div className="mt-2 text-text-secondary body-md-regular">
|
||||
<div className="mt-2 body-md-regular text-text-secondary">
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
{closable
|
||||
&& (
|
||||
<div className="absolute right-6 top-6 z-10 flex h-5 w-5 items-center justify-center rounded-2xl hover:cursor-pointer hover:bg-state-base-hover">
|
||||
<div className="absolute top-6 right-6 z-10 flex h-5 w-5 items-center justify-center rounded-2xl hover:cursor-pointer hover:bg-state-base-hover">
|
||||
<span
|
||||
className="i-ri-close-line h-4 w-4 text-text-tertiary"
|
||||
onClick={
|
||||
|
||||
@ -2,13 +2,10 @@
|
||||
|
||||
// z-index strategy (relies on root `isolation: isolate` in layout.tsx):
|
||||
// All base/ui/* overlay primitives — z-1002
|
||||
// Toast stays one layer above overlays at z-1003.
|
||||
// Overlays share the same z-index; DOM order handles stacking when multiple are open.
|
||||
// This ensures overlays inside a Dialog (e.g. a Tooltip on a dialog button) render
|
||||
// above the dialog backdrop instead of being clipped by it.
|
||||
// During migration, z-1002 is chosen to sit above all legacy overlays
|
||||
// (Modal z-[60], PortalToFollowElem callers up to z-[1001]).
|
||||
// Once all legacy overlays are migrated, this can be reduced back to z-50.
|
||||
// Toast uses z-1101 during migration so it stays above legacy highPriority modals.
|
||||
|
||||
import { Dialog as BaseDialog } from '@base-ui/react/dialog'
|
||||
import * as React from 'react'
|
||||
@ -60,7 +57,7 @@ export function DialogContent({
|
||||
<BaseDialog.Backdrop
|
||||
{...backdropProps}
|
||||
className={cn(
|
||||
'inset-0 fixed z-1002 bg-background-overlay',
|
||||
'fixed inset-0 z-1002 bg-background-overlay',
|
||||
'transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none',
|
||||
overlayClassName,
|
||||
backdropProps?.className,
|
||||
|
||||
@ -33,7 +33,7 @@ describe('base/ui/toast', () => {
|
||||
expect(screen.getByText('Your changes are available now.')).toBeInTheDocument()
|
||||
const viewport = screen.getByRole('region', { name: 'Notifications' })
|
||||
expect(viewport).toHaveAttribute('aria-live', 'polite')
|
||||
expect(viewport).toHaveClass('z-1101')
|
||||
expect(viewport).toHaveClass('z-1003')
|
||||
expect(viewport.firstElementChild).toHaveClass('top-4')
|
||||
expect(screen.getByRole('dialog')).not.toHaveClass('outline-hidden')
|
||||
expect(document.body.querySelector('[aria-hidden="true"].i-ri-checkbox-circle-fill')).toBeInTheDocument()
|
||||
|
||||
@ -222,8 +222,7 @@ function ToastViewport() {
|
||||
<BaseToast.Viewport
|
||||
aria-label={toastViewportLabel}
|
||||
className={cn(
|
||||
// During overlay migration, toast must stay above legacy highPriority modals (z-[1100]).
|
||||
'inset-0 group/toast-viewport pointer-events-none fixed z-1101 overflow-visible',
|
||||
'group/toast-viewport pointer-events-none fixed inset-0 z-1003 overflow-visible',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
|
||||
@ -4,7 +4,7 @@ import type { FC, KeyboardEvent } from 'react'
|
||||
import { Command } from 'cmdk'
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { Dialog, DialogContent } from '@/app/components/base/ui/dialog'
|
||||
import InstallFromMarketplace from '../plugins/install-plugin/install-from-marketplace'
|
||||
import { SlashCommandProvider } from './actions/commands'
|
||||
import { slashCommandRegistry } from './actions/commands/registry'
|
||||
@ -143,14 +143,14 @@ const GotoAnything: FC<Props> = ({
|
||||
return (
|
||||
<>
|
||||
<SlashCommandProvider />
|
||||
<Modal
|
||||
isShow={show}
|
||||
onClose={modalClose}
|
||||
closable={false}
|
||||
className="w-[480px]! p-0!"
|
||||
highPriority={true}
|
||||
<Dialog
|
||||
open={show}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
modalClose()
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col rounded-2xl border border-components-panel-border bg-components-panel-bg shadow-xl">
|
||||
<DialogContent className="w-[480px]! overflow-hidden p-0!">
|
||||
<Command
|
||||
className="outline-hidden"
|
||||
value={cmdVal}
|
||||
@ -219,8 +219,8 @@ const GotoAnything: FC<Props> = ({
|
||||
hasQuery={!!searchQuery.trim()}
|
||||
/>
|
||||
</Command>
|
||||
</div>
|
||||
</Modal>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{activePlugin && (
|
||||
<InstallFromMarketplace
|
||||
|
||||
@ -93,10 +93,10 @@ const NormalForm = () => {
|
||||
<div className="rounded-lg bg-linear-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4">
|
||||
<div className="shadows-shadow-lg relative mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow">
|
||||
<RiContractLine className="h-5 w-5" />
|
||||
<RiErrorWarningFill className="absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary" />
|
||||
<RiErrorWarningFill className="absolute -top-1 -right-1 h-4 w-4 text-text-warning-secondary" />
|
||||
</div>
|
||||
<p className="text-text-primary system-sm-medium">{t('licenseLost', { ns: 'login' })}</p>
|
||||
<p className="mt-1 text-text-tertiary system-xs-regular">{t('licenseLostTip', { ns: 'login' })}</p>
|
||||
<p className="system-sm-medium text-text-primary">{t('licenseLost', { ns: 'login' })}</p>
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">{t('licenseLostTip', { ns: 'login' })}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -109,10 +109,10 @@ const NormalForm = () => {
|
||||
<div className="rounded-lg bg-linear-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4">
|
||||
<div className="shadows-shadow-lg relative mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow">
|
||||
<RiContractLine className="h-5 w-5" />
|
||||
<RiErrorWarningFill className="absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary" />
|
||||
<RiErrorWarningFill className="absolute -top-1 -right-1 h-4 w-4 text-text-warning-secondary" />
|
||||
</div>
|
||||
<p className="text-text-primary system-sm-medium">{t('licenseExpired', { ns: 'login' })}</p>
|
||||
<p className="mt-1 text-text-tertiary system-xs-regular">{t('licenseExpiredTip', { ns: 'login' })}</p>
|
||||
<p className="system-sm-medium text-text-primary">{t('licenseExpired', { ns: 'login' })}</p>
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">{t('licenseExpiredTip', { ns: 'login' })}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -125,10 +125,10 @@ const NormalForm = () => {
|
||||
<div className="rounded-lg bg-linear-to-r from-workflow-workflow-progress-bg-1 to-workflow-workflow-progress-bg-2 p-4">
|
||||
<div className="shadows-shadow-lg relative mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow">
|
||||
<RiContractLine className="h-5 w-5" />
|
||||
<RiErrorWarningFill className="absolute -right-1 -top-1 h-4 w-4 text-text-warning-secondary" />
|
||||
<RiErrorWarningFill className="absolute -top-1 -right-1 h-4 w-4 text-text-warning-secondary" />
|
||||
</div>
|
||||
<p className="text-text-primary system-sm-medium">{t('licenseInactive', { ns: 'login' })}</p>
|
||||
<p className="mt-1 text-text-tertiary system-xs-regular">{t('licenseInactiveTip', { ns: 'login' })}</p>
|
||||
<p className="system-sm-medium text-text-primary">{t('licenseInactive', { ns: 'login' })}</p>
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">{t('licenseInactiveTip', { ns: 'login' })}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -141,12 +141,12 @@ const NormalForm = () => {
|
||||
{isInviteLink
|
||||
? (
|
||||
<div className="mx-auto w-full">
|
||||
<h2 className="text-text-primary title-4xl-semi-bold">
|
||||
<h2 className="title-4xl-semi-bold text-text-primary">
|
||||
{t('join', { ns: 'login' })}
|
||||
{workspaceName}
|
||||
</h2>
|
||||
{!systemFeatures.branding.enabled && (
|
||||
<p className="mt-2 text-text-tertiary body-md-regular">
|
||||
<p className="mt-2 body-md-regular text-text-tertiary">
|
||||
{t('joinTipStart', { ns: 'login' })}
|
||||
{workspaceName}
|
||||
{t('joinTipEnd', { ns: 'login' })}
|
||||
@ -156,8 +156,8 @@ const NormalForm = () => {
|
||||
)
|
||||
: (
|
||||
<div className="mx-auto w-full">
|
||||
<h2 className="text-text-primary title-4xl-semi-bold">{systemFeatures.branding.enabled ? t('pageTitleForE', { ns: 'login' }) : t('pageTitle', { ns: 'login' })}</h2>
|
||||
<p className="mt-2 text-text-tertiary body-md-regular">{t('welcome', { ns: 'login' })}</p>
|
||||
<h2 className="title-4xl-semi-bold text-text-primary">{systemFeatures.branding.enabled ? t('pageTitleForE', { ns: 'login' }) : t('pageTitle', { ns: 'login' })}</h2>
|
||||
<p className="mt-2 body-md-regular text-text-tertiary">{t('welcome', { ns: 'login' })}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="relative">
|
||||
@ -174,7 +174,7 @@ const NormalForm = () => {
|
||||
<div className="relative mt-6">
|
||||
<div className="flex items-center">
|
||||
<div className="h-px flex-1 bg-linear-to-r from-background-gradient-mask-transparent to-divider-regular"></div>
|
||||
<span className="px-3 text-text-tertiary system-xs-medium-uppercase">{t('or', { ns: 'login' })}</span>
|
||||
<span className="px-3 system-xs-medium-uppercase text-text-tertiary">{t('or', { ns: 'login' })}</span>
|
||||
<div className="h-px flex-1 bg-linear-to-l from-background-gradient-mask-transparent to-divider-regular"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -187,7 +187,7 @@ const NormalForm = () => {
|
||||
<MailAndCodeAuth isInvite={isInviteLink} />
|
||||
{systemFeatures.enable_email_password_login && (
|
||||
<div className="cursor-pointer py-1 text-center" onClick={() => { updateAuthType('password') }}>
|
||||
<span className="text-components-button-secondary-accent-text system-xs-medium">{t('usePassword', { ns: 'login' })}</span>
|
||||
<span className="system-xs-medium text-components-button-secondary-accent-text">{t('usePassword', { ns: 'login' })}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
@ -197,18 +197,18 @@ const NormalForm = () => {
|
||||
<MailAndPasswordAuth isInvite={isInviteLink} isEmailSetup={systemFeatures.is_email_setup} allowRegistration={systemFeatures.is_allow_register} />
|
||||
{systemFeatures.enable_email_code_login && (
|
||||
<div className="cursor-pointer py-1 text-center" onClick={() => { updateAuthType('code') }}>
|
||||
<span className="text-components-button-secondary-accent-text system-xs-medium">{t('useVerificationCode', { ns: 'login' })}</span>
|
||||
<span className="system-xs-medium text-components-button-secondary-accent-text">{t('useVerificationCode', { ns: 'login' })}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Split className="mb-5 mt-4" />
|
||||
<Split className="mt-4 mb-5" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
{systemFeatures.is_allow_register && authType === 'password' && (
|
||||
<div className="mb-3 text-[13px] font-medium leading-4 text-text-secondary">
|
||||
<div className="mb-3 text-[13px] leading-4 font-medium text-text-secondary">
|
||||
<span>{t('signup.noAccount', { ns: 'login' })}</span>
|
||||
<Link
|
||||
className="text-text-accent"
|
||||
@ -224,8 +224,8 @@ const NormalForm = () => {
|
||||
<div className="shadows-shadow-lg mb-2 flex h-10 w-10 items-center justify-center rounded-xl bg-components-card-bg shadow">
|
||||
<RiDoorLockLine className="h-5 w-5" />
|
||||
</div>
|
||||
<p className="text-text-primary system-sm-medium">{t('noLoginMethod', { ns: 'login' })}</p>
|
||||
<p className="mt-1 text-text-tertiary system-xs-regular">{t('noLoginMethodTip', { ns: 'login' })}</p>
|
||||
<p className="system-sm-medium text-text-primary">{t('noLoginMethod', { ns: 'login' })}</p>
|
||||
<p className="mt-1 system-xs-regular text-text-tertiary">{t('noLoginMethodTip', { ns: 'login' })}</p>
|
||||
</div>
|
||||
<div className="relative my-2 py-2">
|
||||
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
@ -236,11 +236,11 @@ const NormalForm = () => {
|
||||
)}
|
||||
{!systemFeatures.branding.enabled && (
|
||||
<>
|
||||
<div className="mt-2 block w-full text-text-tertiary system-xs-regular">
|
||||
<div className="mt-2 block w-full system-xs-regular text-text-tertiary">
|
||||
{t('tosDesc', { ns: 'login' })}
|
||||
|
||||
<Link
|
||||
className="text-text-secondary system-xs-medium hover:underline"
|
||||
className="system-xs-medium text-text-secondary hover:underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://dify.ai/terms"
|
||||
@ -249,7 +249,7 @@ const NormalForm = () => {
|
||||
</Link>
|
||||
&
|
||||
<Link
|
||||
className="text-text-secondary system-xs-medium hover:underline"
|
||||
className="system-xs-medium text-text-secondary hover:underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://dify.ai/privacy"
|
||||
@ -258,11 +258,11 @@ const NormalForm = () => {
|
||||
</Link>
|
||||
</div>
|
||||
{IS_CE_EDITION && (
|
||||
<div className="w-hull mt-2 block text-text-tertiary system-xs-regular">
|
||||
<div className="w-hull mt-2 block system-xs-regular text-text-tertiary">
|
||||
{t('goToInit', { ns: 'login' })}
|
||||
|
||||
<Link
|
||||
className="text-text-secondary system-xs-medium hover:underline"
|
||||
className="system-xs-medium text-text-secondary hover:underline"
|
||||
href="/install"
|
||||
>
|
||||
{t('setAdminAccount', { ns: 'login' })}
|
||||
|
||||
@ -64,7 +64,7 @@ pnpm -C web lint:fix --prune-suppressions <changed-files>
|
||||
## z-index strategy
|
||||
|
||||
All new overlay primitives in `base/ui/` share a single z-index value:
|
||||
**`z-1002`**, except Toast which stays at **`z-1101`** during migration.
|
||||
**`z-1002`**, except Toast which stays one layer above at **`z-1003`**.
|
||||
|
||||
### Why z-[1002]?
|
||||
|
||||
@ -77,16 +77,15 @@ portal to `document.body` with explicit z-index values:
|
||||
| Legacy Modal | `z-60` | `base/modal` (default) |
|
||||
| Legacy PortalToFollowElem callers | up to `z-1001` | various business components |
|
||||
| **New UI primitives** | **`z-1002`** | `base/ui/*` (Popover, Dialog, Tooltip, etc.) |
|
||||
| Legacy Modal (highPriority) | `z-1100` | `base/modal` (`highPriority={true}`) |
|
||||
| Toast | `z-1101` | `base/ui/toast` |
|
||||
| Toast | `z-1003` | `base/ui/toast` |
|
||||
|
||||
`z-1002` sits above all common legacy overlays, so new primitives always
|
||||
render on top without needing per-call-site z-index hacks. Among themselves,
|
||||
new primitives share the same z-index and rely on **DOM order** for stacking
|
||||
(later portal = on top).
|
||||
|
||||
Toast stays one layer above the remaining legacy `highPriority` modal path
|
||||
(`z-1100`) so notifications keep their current visibility without falling
|
||||
Toast stays one layer above the overlay primitives so notifications remain
|
||||
visible above dialogs, popovers, and other portalled surfaces without falling
|
||||
back to `z-9999`.
|
||||
|
||||
### Rules
|
||||
@ -104,7 +103,7 @@ back to `z-9999`.
|
||||
Once all legacy overlays are removed:
|
||||
|
||||
1. Reduce `z-1002` back to `z-50` across all `base/ui/` primitives.
|
||||
1. Reduce Toast from `z-1101` to `z-51`.
|
||||
1. Reduce Toast from `z-1003` to `z-51`.
|
||||
1. Remove this section from the migration guide.
|
||||
|
||||
## React Refresh policy for base UI primitives
|
||||
|
||||
@ -3097,11 +3097,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/modal/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/base/modal/modal.stories.tsx": {
|
||||
"no-console": {
|
||||
"count": 4
|
||||
@ -3776,16 +3771,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/dialog/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/ui/toast/index.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/video-gallery/VideoPlayer.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@ -5740,11 +5725,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/goto-anything/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/header/account-about/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -11501,11 +11481,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/signin/normal-form.tsx": {
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 25
|
||||
}
|
||||
},
|
||||
"app/signin/one-more-step.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
|
||||
Loading…
Reference in New Issue
Block a user