diff --git a/web/app/components/header/account-dropdown/compliance.spec.tsx b/web/app/components/header/account-dropdown/compliance.spec.tsx index 54a0460f82..1eb747e154 100644 --- a/web/app/components/header/account-dropdown/compliance.spec.tsx +++ b/web/app/components/header/account-dropdown/compliance.spec.tsx @@ -1,6 +1,7 @@ import type { ModalContextState } from '@/context/modal-context' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/app/components/base/ui/dropdown-menu' import { Plan } from '@/app/components/billing/type' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { useModalContext } from '@/context/modal-context' @@ -70,16 +71,26 @@ describe('Compliance', () => { ) } - // Wrapper for tests that need the menu open + const renderCompliance = () => { + return renderWithQueryClient( + {}}> + open + + + + , + ) + } + const openMenuAndRender = () => { - renderWithQueryClient() - fireEvent.click(screen.getByRole('button')) + renderCompliance() + fireEvent.click(screen.getByText('common.userProfile.compliance')) } describe('Rendering', () => { it('should render compliance menu trigger', () => { // Act - renderWithQueryClient() + renderCompliance() // Assert expect(screen.getByText('common.userProfile.compliance')).toBeInTheDocument() diff --git a/web/app/components/header/account-dropdown/compliance.tsx b/web/app/components/header/account-dropdown/compliance.tsx index 6bc5b5c3f1..ac4d06da5b 100644 --- a/web/app/components/header/account-dropdown/compliance.tsx +++ b/web/app/components/header/account-dropdown/compliance.tsx @@ -1,9 +1,9 @@ -import type { FC, MouseEvent } from 'react' -import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' -import { RiArrowDownCircleLine, RiArrowRightSLine, RiVerifiedBadgeLine } from '@remixicon/react' +import type { ReactNode } from 'react' import { useMutation } from '@tanstack/react-query' -import { Fragment, useCallback } from 'react' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' +import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@/app/components/base/ui/dropdown-menu' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' import { Plan } from '@/app/components/billing/type' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { useModalContext } from '@/context/modal-context' @@ -11,14 +11,19 @@ import { useProviderContext } from '@/context/provider-context' import { getDocDownloadUrl } from '@/service/common' import { cn } from '@/utils/classnames' import { downloadUrl } from '@/utils/download' -import Button from '../../base/button' import Gdpr from '../../base/icons/src/public/common/Gdpr' import Iso from '../../base/icons/src/public/common/Iso' import Soc2 from '../../base/icons/src/public/common/Soc2' import SparklesSoft from '../../base/icons/src/public/common/SparklesSoft' import PremiumBadge from '../../base/premium-badge' +import Spinner from '../../base/spinner' import Toast from '../../base/toast' -import Tooltip from '../../base/tooltip' + +const submenuTriggerClassName = '!mx-0 !h-8 !rounded-lg !px-3 data-[highlighted]:!bg-state-base-hover' +const submenuItemClassName = '!mx-0 !h-10 !rounded-lg !py-1 !pl-1 !pr-2 data-[highlighted]:!bg-state-base-hover' +const menuLabelClassName = 'grow px-1 text-text-secondary system-md-regular' +const menuLeadingIconClassName = 'size-4 shrink-0 text-text-tertiary' +const menuTrailingIconClassName = 'size-[14px] shrink-0 text-text-tertiary' enum DocName { SOC2_Type_I = 'SOC2_Type_I', @@ -27,27 +32,103 @@ enum DocName { GDPR = 'GDPR', } -type UpgradeOrDownloadProps = { - doc_name: DocName +type ComplianceMenuItemContentProps = { + iconClassName: string + label: ReactNode + trailing?: ReactNode } -const UpgradeOrDownload: FC = ({ doc_name }) => { + +function ComplianceMenuItemContent({ + iconClassName, + label, + trailing, +}: ComplianceMenuItemContentProps) { + return ( + <> + +
{label}
+ {trailing} + + ) +} + +type ComplianceDocActionVisualProps = { + isCurrentPlanCanDownload: boolean + isPending: boolean + tooltipText: string + downloadText: string + upgradeText: string +} + +function ComplianceDocActionVisual({ + isCurrentPlanCanDownload, + isPending, + tooltipText, + downloadText, + upgradeText, +}: ComplianceDocActionVisualProps) { + if (isCurrentPlanCanDownload) { + return ( +
+ + {downloadText} + {isPending && } +
+ ) + } + + const canShowUpgradeTooltip = tooltipText.length > 0 + + return ( + + + +
+ {upgradeText} +
+ + )} + /> + {canShowUpgradeTooltip && ( + + {tooltipText} + + )} +
+ ) +} + +type ComplianceDocRowItemProps = { + icon: ReactNode + label: ReactNode + docName: DocName +} + +function ComplianceDocRowItem({ + icon, + label, + docName, +}: ComplianceDocRowItemProps) { const { t } = useTranslation() const { plan } = useProviderContext() const { setShowPricingModal, setShowAccountSettingModal } = useModalContext() const isFreePlan = plan.type === Plan.sandbox - const handlePlanClick = useCallback(() => { - if (isFreePlan) - setShowPricingModal() - else - setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.BILLING }) - }, [isFreePlan, setShowAccountSettingModal, setShowPricingModal]) - const { isPending, mutate: downloadCompliance } = useMutation({ - mutationKey: ['downloadCompliance', doc_name], + mutationKey: ['downloadCompliance', docName], mutationFn: async () => { try { - const ret = await getDocDownloadUrl(doc_name) + const ret = await getDocDownloadUrl(docName) downloadUrl({ url: ret.url }) Toast.notify({ type: 'success', @@ -63,6 +144,7 @@ const UpgradeOrDownload: FC = ({ doc_name }) => { } }, }) + const whichPlanCanDownloadCompliance = { [DocName.SOC2_Type_I]: [Plan.professional, Plan.team], [DocName.SOC2_Type_II]: [Plan.team], @@ -70,118 +152,86 @@ const UpgradeOrDownload: FC = ({ doc_name }) => { [DocName.GDPR]: [Plan.team, Plan.professional, Plan.sandbox], } - const isCurrentPlanCanDownload = whichPlanCanDownloadCompliance[doc_name].includes(plan.type) - const handleDownloadClick = useCallback((e: MouseEvent) => { - e.preventDefault() - downloadCompliance() - }, [downloadCompliance]) - if (isCurrentPlanCanDownload) { - return ( - - ) - } + const isCurrentPlanCanDownload = whichPlanCanDownloadCompliance[docName].includes(plan.type) + + const handleSelect = useCallback(() => { + if (isCurrentPlanCanDownload) { + if (!isPending) + downloadCompliance() + return + } + + if (isFreePlan) + setShowPricingModal() + else + setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.BILLING }) + }, [downloadCompliance, isCurrentPlanCanDownload, isFreePlan, isPending, setShowAccountSettingModal, setShowPricingModal]) + const upgradeTooltip: Record = { [Plan.sandbox]: t('compliance.sandboxUpgradeTooltip', { ns: 'common' }), [Plan.professional]: t('compliance.professionalUpgradeTooltip', { ns: 'common' }), [Plan.team]: '', [Plan.enterprise]: '', } + return ( - - - -
- - {t('upgradeBtn.encourageShort', { ns: 'billing' })} - -
-
-
+ + {icon} +
{label}
+ +
) } +// Submenu-only: this component must be rendered within an existing DropdownMenu root. export default function Compliance() { - const itemClassName = ` - flex items-center w-full h-10 pl-1 pr-2 py-1 text-text-secondary system-md-regular - rounded-lg hover:bg-state-base-hover gap-1 -` const { t } = useTranslation() return ( - - { - ({ open }) => ( - <> - - -
{t('userProfile.compliance', { ns: 'common' })}
- -
- - -
- -
- -
{t('compliance.soc2Type1', { ns: 'common' })}
- -
-
- -
- -
{t('compliance.soc2Type2', { ns: 'common' })}
- -
-
- -
- -
{t('compliance.iso27001', { ns: 'common' })}
- -
-
- -
- -
{t('compliance.gdpr', { ns: 'common' })}
- -
-
-
-
-
- - ) - } -
+ + + } + /> + + + + } + label={t('compliance.soc2Type1', { ns: 'common' })} + docName={DocName.SOC2_Type_I} + /> + } + label={t('compliance.soc2Type2', { ns: 'common' })} + docName={DocName.SOC2_Type_II} + /> + } + label={t('compliance.iso27001', { ns: 'common' })} + docName={DocName.ISO_27001} + /> + } + label={t('compliance.gdpr', { ns: 'common' })} + docName={DocName.GDPR} + /> + + + ) } diff --git a/web/app/components/header/account-dropdown/index.spec.tsx b/web/app/components/header/account-dropdown/index.spec.tsx index a954351267..60e00868c1 100644 --- a/web/app/components/header/account-dropdown/index.spec.tsx +++ b/web/app/components/header/account-dropdown/index.spec.tsx @@ -65,6 +65,7 @@ vi.mock('@/context/i18n', () => ({ const { mockConfig, mockEnv } = vi.hoisted(() => ({ mockConfig: { IS_CLOUD_EDITION: false, + ZENDESK_WIDGET_KEY: '', }, mockEnv: { env: { @@ -74,6 +75,7 @@ const { mockConfig, mockEnv } = vi.hoisted(() => ({ })) vi.mock('@/config', () => ({ get IS_CLOUD_EDITION() { return mockConfig.IS_CLOUD_EDITION }, + get ZENDESK_WIDGET_KEY() { return mockConfig.ZENDESK_WIDGET_KEY }, IS_DEV: false, IS_CE_EDITION: false, })) @@ -187,6 +189,14 @@ describe('AccountDropdown', () => { expect(screen.getByText('test@example.com')).toBeInTheDocument() }) + it('should set an accessible label on avatar trigger when menu trigger is rendered', () => { + // Act + renderWithRouter() + + // Assert + expect(screen.getByRole('button', { name: 'common.account.account' })).toBeInTheDocument() + }) + it('should show EDU badge for education accounts', () => { // Arrange vi.mocked(useProviderContext).mockReturnValue({ diff --git a/web/app/components/header/account-dropdown/index.tsx b/web/app/components/header/account-dropdown/index.tsx index 983f9e434d..6ba54073f3 100644 --- a/web/app/components/header/account-dropdown/index.tsx +++ b/web/app/components/header/account-dropdown/index.tsx @@ -1,26 +1,15 @@ 'use client' -import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' -import { - RiAccountCircleLine, - RiArrowRightUpLine, - RiBookOpenLine, - RiGithubLine, - RiGraduationCapFill, - RiInformation2Line, - RiLogoutBoxRLine, - RiMap2Line, - RiSettings3Line, - RiStarLine, - RiTShirt2Line, -} from '@remixicon/react' + +import type { MouseEventHandler, ReactNode } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' -import { Fragment, useState } from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { resetUser } from '@/app/components/base/amplitude/utils' import Avatar from '@/app/components/base/avatar' import PremiumBadge from '@/app/components/base/premium-badge' import ThemeSwitcher from '@/app/components/base/theme-switcher' +import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/app/components/base/ui/dropdown-menu' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { IS_CLOUD_EDITION } from '@/config' import { useAppContext } from '@/context/app-context' @@ -37,13 +26,117 @@ import Indicator from '../indicator' import Compliance from './compliance' import Support from './support' +const menuItemClassName = '!mx-0 !h-8 !rounded-lg !px-3 data-[highlighted]:!bg-state-base-hover' +const menuStaticRowClassName = 'flex h-8 w-full items-center rounded-lg px-3 text-text-secondary system-md-regular' +const menuLabelClassName = 'grow px-1 text-text-secondary system-md-regular' +const menuLeadingIconClassName = 'size-4 shrink-0 text-text-tertiary' +const menuTrailingIconClassName = 'size-[14px] shrink-0 text-text-tertiary' + +type AccountMenuItemContentProps = { + iconClassName: string + label: ReactNode + trailing?: ReactNode +} + +function AccountMenuItemContent({ + iconClassName, + label, + trailing, +}: AccountMenuItemContentProps) { + return ( + <> + +
{label}
+ {trailing} + + ) +} + +type AccountMenuRouteItemProps = { + href: string + iconClassName: string + label: ReactNode + trailing?: ReactNode +} + +function AccountMenuRouteItem({ + href, + iconClassName, + label, + trailing, +}: AccountMenuRouteItemProps) { + return ( + } + > + + + ) +} + +type AccountMenuExternalItemProps = { + href: string + iconClassName: string + label: ReactNode + trailing?: ReactNode +} + +function AccountMenuExternalItem({ + href, + iconClassName, + label, + trailing, +}: AccountMenuExternalItemProps) { + return ( + } + > + + + ) +} + +type AccountMenuActionItemProps = { + iconClassName: string + label: ReactNode + onClick?: MouseEventHandler + trailing?: ReactNode +} + +function AccountMenuActionItem({ + iconClassName, + label, + onClick, + trailing, +}: AccountMenuActionItemProps) { + return ( + + + + ) +} + +function ExternalLinkIndicator() { + return +} + +type AccountMenuSectionProps = { + children: ReactNode +} + +function AccountMenuSection({ children }: AccountMenuSectionProps) { + return {children} +} + export default function AppSelector() { - const itemClassName = ` - flex items-center w-full h-8 pl-3 pr-2 text-text-secondary system-md-regular - rounded-lg hover:bg-state-base-hover cursor-pointer gap-1 - ` const router = useRouter() const [aboutVisible, setAboutVisible] = useState(false) + const [isAccountMenuOpen, setIsAccountMenuOpen] = useState(false) const { systemFeatures } = useGlobalPublicStore() const { t } = useTranslation() @@ -68,161 +161,121 @@ export default function AppSelector() { } return ( -
- - { - ({ open, close }) => ( - <> - - - - - -
- -
-
-
- {userProfile.name} - {isEducationAccount && ( - - - EDU - - )} -
-
{userProfile.email}
-
- -
-
- - - -
{t('account.account', { ns: 'common' })}
- - -
- -
setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.MEMBERS })} - > - -
{t('userProfile.settings', { ns: 'common' })}
-
-
-
- {!systemFeatures.branding.enabled && ( - <> -
- - - -
{t('userProfile.helpCenter', { ns: 'common' })}
- - -
- - {IS_CLOUD_EDITION && isCurrentWorkspaceOwner && } -
-
- - - -
{t('userProfile.roadmap', { ns: 'common' })}
- - -
- - - -
{t('userProfile.github', { ns: 'common' })}
-
- - -
- -
- { - env.NEXT_PUBLIC_SITE_ABOUT !== 'hide' && ( - -
setAboutVisible(true)} - > - -
{t('userProfile.about', { ns: 'common' })}
-
-
{langGeniusVersionInfo.current_version}
- -
-
-
- ) - } -
- +
+ + + + + + +
+
+
+ {userProfile.name} + {isEducationAccount && ( + + + EDU + )} - -
-
- -
{t('theme.theme', { ns: 'common' })}
- -
+
+
{userProfile.email}
+
+ +
+ } + /> + setShowAccountSettingModal({ payload: ACCOUNT_SETTING_TAB.MEMBERS })} + /> + + + {!systemFeatures.branding.enabled && ( + <> + + } + /> + setIsAccountMenuOpen(false)} /> + {IS_CLOUD_EDITION && isCurrentWorkspaceOwner && } + + + + } + /> + + +
- - -
handleLogout()}> -
- -
{t('userProfile.logout', { ns: 'common' })}
-
-
-
- - + )} + /> + { + env.NEXT_PUBLIC_SITE_ABOUT !== 'hide' && ( + { + setAboutVisible(true) + setIsAccountMenuOpen(false) + }} + trailing={( +
+
{langGeniusVersionInfo.current_version}
+ +
+ )} + /> + ) + } + + - ) - } -
+ )} + +
+ } + /> +
+
+ + + { + void handleLogout() + }} + /> + + + { aboutVisible && setAboutVisible(false)} langGeniusVersionInfo={langGeniusVersionInfo} /> } diff --git a/web/app/components/header/account-dropdown/support.spec.tsx b/web/app/components/header/account-dropdown/support.spec.tsx index b30a290ea5..90bcb9f3ec 100644 --- a/web/app/components/header/account-dropdown/support.spec.tsx +++ b/web/app/components/header/account-dropdown/support.spec.tsx @@ -1,6 +1,7 @@ import type { AppContextValue } from '@/context/app-context' import { fireEvent, render, screen } from '@testing-library/react' +import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/app/components/base/ui/dropdown-menu' import { Plan } from '@/app/components/billing/type' import { useAppContext } from '@/context/app-context' import { baseProviderContextValue, useProviderContext } from '@/context/provider-context' @@ -93,10 +94,21 @@ describe('Support', () => { }) }) + const renderSupport = () => { + return render( + {}}> + open + + + + , + ) + } + describe('Rendering', () => { it('should render support menu trigger', () => { // Act - render() + renderSupport() // Assert expect(screen.getByText('common.userProfile.support')).toBeInTheDocument() @@ -104,8 +116,8 @@ describe('Support', () => { it('should show forum and community links when opened', () => { // Act - render() - fireEvent.click(screen.getByRole('button')) + renderSupport() + fireEvent.click(screen.getByText('common.userProfile.support')) // Assert expect(screen.getByText('common.userProfile.forum')).toBeInTheDocument() @@ -116,8 +128,8 @@ describe('Support', () => { describe('Plan-based Channels', () => { it('should show "Contact Us" when ZENDESK_WIDGET_KEY is present', () => { // Act - render() - fireEvent.click(screen.getByRole('button')) + renderSupport() + fireEvent.click(screen.getByText('common.userProfile.support')) // Assert expect(screen.getByText('common.userProfile.contactUs')).toBeInTheDocument() @@ -134,8 +146,8 @@ describe('Support', () => { }) // Act - render() - fireEvent.click(screen.getByRole('button')) + renderSupport() + fireEvent.click(screen.getByText('common.userProfile.support')) // Assert expect(screen.queryByText('common.userProfile.contactUs')).not.toBeInTheDocument() @@ -147,8 +159,8 @@ describe('Support', () => { mockZendeskKey.value = '' // Act - render() - fireEvent.click(screen.getByRole('button')) + renderSupport() + fireEvent.click(screen.getByText('common.userProfile.support')) // Assert expect(screen.getByText('common.userProfile.emailSupport')).toBeInTheDocument() @@ -159,8 +171,8 @@ describe('Support', () => { describe('Interactions and Links', () => { it('should call toggleZendeskWindow and closeAccountDropdown when "Contact Us" is clicked', () => { // Act - render() - fireEvent.click(screen.getByRole('button')) + renderSupport() + fireEvent.click(screen.getByText('common.userProfile.support')) fireEvent.click(screen.getByText('common.userProfile.contactUs')) // Assert @@ -170,8 +182,8 @@ describe('Support', () => { it('should have correct forum and community links', () => { // Act - render() - fireEvent.click(screen.getByRole('button')) + renderSupport() + fireEvent.click(screen.getByText('common.userProfile.support')) // Assert const forumLink = screen.getByText('common.userProfile.forum').closest('a') diff --git a/web/app/components/header/account-dropdown/support.tsx b/web/app/components/header/account-dropdown/support.tsx index 7873b676c3..fc3d3ab7f9 100644 --- a/web/app/components/header/account-dropdown/support.tsx +++ b/web/app/components/header/account-dropdown/support.tsx @@ -1,8 +1,6 @@ -import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' -import { RiArrowRightSLine, RiArrowRightUpLine, RiChatSmile2Line, RiDiscordLine, RiDiscussLine, RiMailSendLine, RiQuestionLine } from '@remixicon/react' -import Link from 'next/link' -import { Fragment } from 'react' +import type { ReactNode } from 'react' import { useTranslation } from 'react-i18next' +import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger } from '@/app/components/base/ui/dropdown-menu' import { toggleZendeskWindow } from '@/app/components/base/zendesk/utils' import { Plan } from '@/app/components/billing/type' import { ZENDESK_WIDGET_KEY } from '@/config' @@ -11,109 +9,109 @@ import { useProviderContext } from '@/context/provider-context' import { cn } from '@/utils/classnames' import { mailToSupport } from '../utils/util' +const submenuTriggerClassName = '!mx-0 !h-8 !rounded-lg !px-3 data-[highlighted]:!bg-state-base-hover' +const submenuItemClassName = '!mx-0 !h-8 !rounded-lg !px-3 data-[highlighted]:!bg-state-base-hover' +const menuLabelClassName = 'grow px-1 text-text-secondary system-md-regular' +const menuLeadingIconClassName = 'size-4 shrink-0 text-text-tertiary' +const menuTrailingIconClassName = 'size-[14px] shrink-0 text-text-tertiary' + type SupportProps = { closeAccountDropdown: () => void } +type SupportMenuItemContentProps = { + iconClassName: string + label: ReactNode + trailing?: ReactNode +} + +function SupportMenuItemContent({ + iconClassName, + label, + trailing, +}: SupportMenuItemContentProps) { + return ( + <> + +
{label}
+ {trailing} + + ) +} + +function SupportExternalLinkIndicator() { + return +} + +// Submenu-only: this component must be rendered within an existing DropdownMenu root. export default function Support({ closeAccountDropdown }: SupportProps) { - const itemClassName = ` - flex items-center w-full h-9 pl-3 pr-2 text-text-secondary system-md-regular - rounded-lg hover:bg-state-base-hover cursor-pointer gap-1 -` const { t } = useTranslation() const { plan } = useProviderContext() const { userProfile, langGeniusVersionInfo } = useAppContext() const hasDedicatedChannel = plan.type !== Plan.sandbox + const hasZendeskWidget = !!ZENDESK_WIDGET_KEY?.trim() return ( - - { - ({ open }) => ( - <> - + + } + /> + + + + {hasDedicatedChannel && hasZendeskWidget && ( + { + toggleZendeskWindow(true) + closeAccountDropdown() + }} > - -
{t('userProfile.support', { ns: 'common' })}
- -
- + + )} + {hasDedicatedChannel && !hasZendeskWidget && ( + } > - -
- {hasDedicatedChannel && ( - - {ZENDESK_WIDGET_KEY && ZENDESK_WIDGET_KEY.trim() !== '' - ? ( - - ) - : ( - - -
{t('userProfile.emailSupport', { ns: 'common' })}
- -
- )} -
- )} - - - -
{t('userProfile.forum', { ns: 'common' })}
- - -
- - - -
{t('userProfile.community', { ns: 'common' })}
- - -
-
-
-
- - ) - } -
+ } + /> + + )} + } + > + } + /> + + } + > + } + /> + + + + ) } diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 89335fb765..bfaf48d962 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -3961,21 +3961,6 @@ "count": 1 } }, - "app/components/header/account-dropdown/compliance.tsx": { - "tailwindcss/enforce-consistent-class-order": { - "count": 6 - } - }, - "app/components/header/account-dropdown/index.tsx": { - "tailwindcss/enforce-consistent-class-order": { - "count": 12 - } - }, - "app/components/header/account-dropdown/support.tsx": { - "tailwindcss/enforce-consistent-class-order": { - "count": 5 - } - }, "app/components/header/account-dropdown/workplace-selector/index.tsx": { "tailwindcss/enforce-consistent-class-order": { "count": 3