diff --git a/package.json b/package.json index d803e428d..c909bce22 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "test:visual:update": "scripts/visual.sh --update" }, "dependencies": { + "cn": "0.2.4", "electron-log": "5.4.4", "electron-menubar": "10.2.1", "electron-updater": "6.8.9", @@ -111,7 +112,6 @@ "@vitejs/plugin-react": "6.1.1", "@vitest/browser-playwright": "4.1.11", "@vitest/coverage-v8": "4.1.11", - "clsx": "2.1.1", "concurrently": "10.0.5", "date-fns": "4.4.0", "dotenv": "17.4.2", @@ -125,7 +125,6 @@ "react-devtools": "7.0.1", "rimraf": "6.1.3", "semver": "7.8.5", - "tailwind-merge": "3.6.0", "tailwindcss": "4.3.3", "typescript": "7.0.2", "vite": "8.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fa81e6b4..0135b0a62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: .: dependencies: + cn: + specifier: 0.2.4 + version: 0.2.4 electron-log: specifier: 5.4.4 version: 5.4.4 @@ -129,9 +132,6 @@ importers: '@vitest/coverage-v8': specifier: 4.1.11 version: 4.1.11(@vitest/browser@4.1.11)(vitest@4.1.11) - clsx: - specifier: 2.1.1 - version: 2.1.1 concurrently: specifier: 10.0.5 version: 10.0.5 @@ -171,9 +171,6 @@ importers: semver: specifier: 7.8.5 version: 7.8.5 - tailwind-merge: - specifier: 3.6.0 - version: 3.6.0 tailwindcss: specifier: 4.3.3 version: 4.3.3 @@ -2321,6 +2318,11 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + cn@0.2.4: + resolution: {integrity: sha512-SzQvoh5FwizWtQg9+JueKdjWhlfCZMdu5DqNHm9q1wUlRVmKb9WXlepR9bTTbPwzEOyX1ujJ6lcCppmn3bNUCg==} + engines: {node: '>=20'} + hasBin: true + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -4153,9 +4155,6 @@ packages: resolution: {integrity: sha512-IELLEvzHuCfc1uTsshPK58ViSdNqXxlml1U+fmwJIKLYKOr/rAtBrorE2RYm5IHaMpDNlmC0fr1LAvdXvyheEQ==} engines: {node: '>=18'} - tailwind-merge@3.6.0: - resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} - tailwindcss@4.3.3: resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} @@ -6629,6 +6628,8 @@ snapshots: clsx@2.1.1: {} + cn@0.2.4: {} + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -8407,8 +8408,6 @@ snapshots: timeout-signal: 2.0.0 whatwg-mimetype: 4.0.0 - tailwind-merge@3.6.0: {} - tailwindcss@4.3.3: {} tapable@2.3.3: {} diff --git a/src/renderer/components/fields/Checkbox.tsx b/src/renderer/components/fields/Checkbox.tsx index 8ed7c30c2..3b21f7c7d 100644 --- a/src/renderer/components/fields/Checkbox.tsx +++ b/src/renderer/components/fields/Checkbox.tsx @@ -2,9 +2,10 @@ import type { FC, ReactNode } from 'react'; import { Stack } from '@primer/react'; +import { cn } from 'cn'; + import { CustomCounter } from '../primitives/CustomCounter'; -import { cn } from '../../utils/ui/cn'; import { Tooltip } from './Tooltip'; export interface CheckboxProps { diff --git a/src/renderer/components/fields/Tooltip.tsx b/src/renderer/components/fields/Tooltip.tsx index f8b103a86..1c340ca2d 100644 --- a/src/renderer/components/fields/Tooltip.tsx +++ b/src/renderer/components/fields/Tooltip.tsx @@ -3,7 +3,7 @@ import { type FC, type ReactNode, useEffect, useRef, useState } from 'react'; import { QuestionIcon } from '@primer/octicons-react'; import { AnchoredOverlay } from '@primer/react'; -import { cn } from '../../utils/ui/cn'; +import { cn } from 'cn'; export interface TooltipProps { name: string; diff --git a/src/renderer/components/filters/SearchFilter.tsx b/src/renderer/components/filters/SearchFilter.tsx index de156d253..a03a4a892 100644 --- a/src/renderer/components/filters/SearchFilter.tsx +++ b/src/renderer/components/filters/SearchFilter.tsx @@ -10,6 +10,8 @@ import { } from '@primer/octicons-react'; import { Stack, Text } from '@primer/react'; +import { cn } from 'cn'; + import { useFiltersStore, useSettingsStore } from '../../stores'; import { Title } from '../primitives/Title'; @@ -20,7 +22,6 @@ import { hasExcludeSearchFilters, hasIncludeSearchFilters, } from '../../utils/notifications/filters/search'; -import { cn } from '../../utils/ui/cn'; import { RequiresDetailedNotificationWarning } from './RequiresDetailedNotificationsWarning'; import { TokenSearchInput } from './TokenSearchInput'; diff --git a/src/renderer/components/filters/SearchFilterSuggestions.tsx b/src/renderer/components/filters/SearchFilterSuggestions.tsx index 3a28538f5..ee211908d 100644 --- a/src/renderer/components/filters/SearchFilterSuggestions.tsx +++ b/src/renderer/components/filters/SearchFilterSuggestions.tsx @@ -2,6 +2,8 @@ import type { FC } from 'react'; import { Popover, Stack, Text } from '@primer/react'; +import { cn } from 'cn'; + import { useSettingsStore } from '../../stores'; import { Opacity } from '../../types'; @@ -11,7 +13,6 @@ import { BASE_SEARCH_QUALIFIERS, SEARCH_DELIMITER, } from '../../utils/notifications/filters/search'; -import { cn } from '../../utils/ui/cn'; interface SearchFilterSuggestionsProps { open: boolean; diff --git a/src/renderer/components/icons/LogoIcon.tsx b/src/renderer/components/icons/LogoIcon.tsx index e568304c1..25c42fae0 100644 --- a/src/renderer/components/icons/LogoIcon.tsx +++ b/src/renderer/components/icons/LogoIcon.tsx @@ -1,11 +1,11 @@ import type { FC } from 'react'; +import { cn } from 'cn'; + import { APPLICATION } from '../../../shared/constants'; import { Size } from '../../types'; -import { cn } from '../../utils/ui/cn'; - interface LogoIconProps { isDark?: boolean; onClick?: () => void; diff --git a/src/renderer/components/layout/Contents.tsx b/src/renderer/components/layout/Contents.tsx index c51ce6ffe..458a59a53 100644 --- a/src/renderer/components/layout/Contents.tsx +++ b/src/renderer/components/layout/Contents.tsx @@ -1,6 +1,6 @@ import type { FC, ReactNode } from 'react'; -import { cn } from '../../utils/ui/cn'; +import { cn } from 'cn'; interface IContents { children: ReactNode; diff --git a/src/renderer/components/notifications/AccountNotifications.tsx b/src/renderer/components/notifications/AccountNotifications.tsx index a2e151413..e2e9fb409 100644 --- a/src/renderer/components/notifications/AccountNotifications.tsx +++ b/src/renderer/components/notifications/AccountNotifications.tsx @@ -3,6 +3,8 @@ import { type FC, type MouseEvent, useMemo, useState } from 'react'; import { GitPullRequestIcon, IssueOpenedIcon } from '@primer/octicons-react'; import { Button, Stack } from '@primer/react'; +import { cn } from 'cn'; + import { useAccountsStore } from '../../stores'; import { HoverButton } from '../primitives/HoverButton'; @@ -16,7 +18,6 @@ import { isGroupByRepository, } from '../../utils/notifications/group'; import { openAccountProfile, openHostIssues, openHostPulls } from '../../utils/system/links'; -import { cn } from '../../utils/ui/cn'; import { getChevronDetails } from '../../utils/ui/display'; import { AllRead } from '../AllRead'; import { AvatarWithFallback } from '../avatars/AvatarWithFallback'; diff --git a/src/renderer/components/notifications/NotificationFooter.tsx b/src/renderer/components/notifications/NotificationFooter.tsx index e00d62582..48a68246a 100644 --- a/src/renderer/components/notifications/NotificationFooter.tsx +++ b/src/renderer/components/notifications/NotificationFooter.tsx @@ -2,11 +2,12 @@ import type { FC, MouseEvent } from 'react'; import { RelativeTime, Stack, Text } from '@primer/react'; +import { cn } from 'cn'; + import { type GitifyNotification, Opacity, Size } from '../../types'; import { getAdapter } from '../../utils/forges/registry'; import { openUserProfile } from '../../utils/system/links'; -import { cn } from '../../utils/ui/cn'; import { AvatarWithFallback } from '../avatars/AvatarWithFallback'; import { MetricGroup } from '../metrics/MetricGroup'; diff --git a/src/renderer/components/notifications/NotificationHeader.tsx b/src/renderer/components/notifications/NotificationHeader.tsx index 072c39685..94ee086ba 100644 --- a/src/renderer/components/notifications/NotificationHeader.tsx +++ b/src/renderer/components/notifications/NotificationHeader.tsx @@ -2,13 +2,14 @@ import type { FC, MouseEvent } from 'react'; import { Stack } from '@primer/react'; +import { cn } from 'cn'; + import { useSettingsStore } from '../../stores'; import { type GitifyNotification, Opacity, Size } from '../../types'; import { isGroupByDate } from '../../utils/notifications/group'; import { openRepository } from '../../utils/system/links'; -import { cn } from '../../utils/ui/cn'; import { AvatarWithFallback } from '../avatars/AvatarWithFallback'; export interface NotificationHeaderProps { diff --git a/src/renderer/components/notifications/NotificationRow.tsx b/src/renderer/components/notifications/NotificationRow.tsx index 4e0bf5777..783600f82 100644 --- a/src/renderer/components/notifications/NotificationRow.tsx +++ b/src/renderer/components/notifications/NotificationRow.tsx @@ -3,6 +3,8 @@ import { type CSSProperties, type FC, useState } from 'react'; import { BellSlashIcon, CheckIcon, ReadIcon } from '@primer/octicons-react'; import { Stack, Text, Tooltip } from '@primer/react'; +import { cn } from 'cn'; + import { useNotifications } from '../../hooks/useNotifications'; import { getNotificationFailureKey, @@ -22,7 +24,6 @@ import { import { isGroupByDate } from '../../utils/notifications/group'; import { shouldRemoveNotificationsFromState } from '../../utils/notifications/remove'; import { openNotification } from '../../utils/system/links'; -import { cn } from '../../utils/ui/cn'; import { NotificationFooter } from './NotificationFooter'; import { NotificationHeader } from './NotificationHeader'; import { NotificationTitle } from './NotificationTitle'; diff --git a/src/renderer/components/notifications/NotificationTitle.tsx b/src/renderer/components/notifications/NotificationTitle.tsx index 891c3c026..35f90559c 100644 --- a/src/renderer/components/notifications/NotificationTitle.tsx +++ b/src/renderer/components/notifications/NotificationTitle.tsx @@ -2,9 +2,10 @@ import type { FC } from 'react'; import { Text } from '@primer/react'; +import { cn } from 'cn'; + import { useSettingsStore } from '../../stores'; -import { cn } from '../../utils/ui/cn'; import { parseInlineCode } from '../../utils/ui/display'; interface NotificationTitleProps { diff --git a/src/renderer/components/notifications/RepositoryNotifications.tsx b/src/renderer/components/notifications/RepositoryNotifications.tsx index acc62c5f7..6fae35d9d 100644 --- a/src/renderer/components/notifications/RepositoryNotifications.tsx +++ b/src/renderer/components/notifications/RepositoryNotifications.tsx @@ -3,6 +3,8 @@ import { type FC, type MouseEvent, useState } from 'react'; import { CheckIcon, ReadIcon } from '@primer/octicons-react'; import { Button, Stack } from '@primer/react'; +import { cn } from 'cn'; + import { useNotifications } from '../../hooks/useNotifications'; import { getNotificationFailureKey, useNotificationActionFailuresStore } from '../../stores'; @@ -14,7 +16,6 @@ import { type GitifyNotification, Opacity, Size } from '../../types'; import { isMarkAsDoneFeatureSupported } from '../../utils/api/features'; import { shouldRemoveNotificationsFromState } from '../../utils/notifications/remove'; import { openRepository } from '../../utils/system/links'; -import { cn } from '../../utils/ui/cn'; import { getChevronDetails } from '../../utils/ui/display'; import { AvatarWithFallback } from '../avatars/AvatarWithFallback'; import { NotificationRow } from './NotificationRow'; diff --git a/src/renderer/components/primitives/CustomCounter.tsx b/src/renderer/components/primitives/CustomCounter.tsx index 5182cacd8..34a53630a 100644 --- a/src/renderer/components/primitives/CustomCounter.tsx +++ b/src/renderer/components/primitives/CustomCounter.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react'; import { Text } from '@primer/react'; -import { cn } from '../../utils/ui/cn'; +import { cn } from 'cn'; type CounterScheme = 'primary' | 'secondary' | 'empty'; diff --git a/src/renderer/components/primitives/HoverGroup.tsx b/src/renderer/components/primitives/HoverGroup.tsx index e61886ccd..ca75f49b1 100644 --- a/src/renderer/components/primitives/HoverGroup.tsx +++ b/src/renderer/components/primitives/HoverGroup.tsx @@ -2,7 +2,7 @@ import type { FC, ReactNode } from 'react'; import { Stack } from '@primer/react'; -import { cn } from '../../utils/ui/cn'; +import { cn } from 'cn'; interface HoverGroupProps { children: ReactNode; diff --git a/src/renderer/routes/Login.tsx b/src/renderer/routes/Login.tsx index ff0c56548..0775fece1 100644 --- a/src/renderer/routes/Login.tsx +++ b/src/renderer/routes/Login.tsx @@ -3,6 +3,8 @@ import { useNavigate } from 'react-router-dom'; import { Button, Heading, Stack, Text } from '@primer/react'; +import { cn } from 'cn'; + import { useAccountsStore } from '../stores'; import { LogoIcon } from '../components/icons/LogoIcon'; @@ -13,7 +15,6 @@ import type { ForgeAdapter, LoginMethodDescriptor } from '../utils/forges/types' import { listAdapters } from '../utils/forges/registry'; import { showWindow } from '../utils/system/comms'; -import { cn } from '../utils/ui/cn'; /** * Pick the method that should drive the dominant CTA for a forge. diff --git a/src/renderer/utils/ui/cn.test.ts b/src/renderer/utils/ui/cn.test.ts deleted file mode 100644 index bbc5b8b65..000000000 --- a/src/renderer/utils/ui/cn.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { cn } from './cn'; - -describe('renderer/utils/cn.ts', () => { - it('should return a string', () => { - // oxlint-disable-next-line no-constant-binary-expression -- Intentional truthy/falsy inputs to exercise cn() - expect(cn('foo', true && 'bar', false && 'baz')).toBe('foo bar'); - }); -}); diff --git a/src/renderer/utils/ui/cn.ts b/src/renderer/utils/ui/cn.ts deleted file mode 100644 index 6ed9e8d07..000000000 --- a/src/renderer/utils/ui/cn.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { type ClassValue, clsx } from 'clsx'; -import { twMerge } from 'tailwind-merge'; - -/** - * Merges class names using clsx and resolves Tailwind CSS conflicts with tailwind-merge. - * - * @param inputs - One or more class values (strings, arrays, objects, etc.). - * @returns A single merged class name string with Tailwind conflicts resolved. - */ -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(...inputs)); -}