diff --git a/src/domains/helpers/DomainDropdown.tsx b/src/domains/helpers/DomainDropdown.tsx index 6d7ba670..8c99e2ac 100644 --- a/src/domains/helpers/DomainDropdown.tsx +++ b/src/domains/helpers/DomainDropdown.tsx @@ -5,9 +5,9 @@ import { Link } from 'react-router-dom'; import { DropdownItem } from 'reactstrap'; import type { SelectedServer } from '../../servers/data'; import { getServerId } from '../../servers/data'; -import { DropdownBtnMenu } from '../../utils/DropdownBtnMenu'; import { useFeature } from '../../utils/helpers/features'; import { useToggle } from '../../utils/helpers/hooks'; +import { RowDropdownBtn } from '../../utils/RowDropdownBtn'; import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits'; import type { Domain } from '../data'; import type { EditDomainRedirects } from '../reducers/domainRedirects'; @@ -20,7 +20,6 @@ interface DomainDropdownProps { } export const DomainDropdown: FC = ({ domain, editDomainRedirects, selectedServer }) => { - const [isOpen, toggle] = useToggle(); const [isModalOpen, toggleModal] = useToggle(); const { isDefault } = domain; const canBeEdited = !isDefault || useFeature('defaultDomainRedirectsEdition', selectedServer); @@ -28,7 +27,7 @@ export const DomainDropdown: FC = ({ domain, editDomainRedi const serverId = getServerId(selectedServer); return ( - + {withVisits && ( = ({ domain, editDomainRedi toggle={toggleModal} editDomainRedirects={editDomainRedirects} /> - + ); }; diff --git a/src/servers/ManageServersRowDropdown.tsx b/src/servers/ManageServersRowDropdown.tsx index 357c0126..9e058df0 100644 --- a/src/servers/ManageServersRowDropdown.tsx +++ b/src/servers/ManageServersRowDropdown.tsx @@ -9,8 +9,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import type { FC } from 'react'; import { Link } from 'react-router-dom'; import { DropdownItem } from 'reactstrap'; -import { DropdownBtnMenu } from '../utils/DropdownBtnMenu'; import { useToggle } from '../utils/helpers/hooks'; +import { RowDropdownBtn } from '../utils/RowDropdownBtn'; import type { ServerWithId } from './data'; import type { DeleteServerModalProps } from './DeleteServerModal'; @@ -25,14 +25,13 @@ interface ManageServersRowDropdownConnectProps extends ManageServersRowDropdownP export const ManageServersRowDropdown = ( DeleteServerModal: FC, ): FC => ({ server, setAutoConnect }) => { - const [isMenuOpen, toggleMenu] = useToggle(); const [isModalOpen,, showModal, hideModal] = useToggle(); const serverUrl = `/server/${server.id}`; const { autoConnect: isAutoConnect } = server; const autoConnectIcon = isAutoConnect ? toggleOffIcon : toggleOnIcon; return ( - + Connect @@ -48,6 +47,6 @@ export const ManageServersRowDropdown = ( - + ); }; diff --git a/src/short-urls/helpers/ShortUrlsFilterDropdown.tsx b/src/short-urls/helpers/ShortUrlsFilterDropdown.tsx index dd8b53e4..16e47395 100644 --- a/src/short-urls/helpers/ShortUrlsFilterDropdown.tsx +++ b/src/short-urls/helpers/ShortUrlsFilterDropdown.tsx @@ -17,7 +17,7 @@ export const ShortUrlsFilterDropdown = ( const onFilterClick = (key: keyof ShortUrlsFilter) => () => onChange({ ...selected, [key]: !selected?.[key] }); return ( - + Visits: Ignore visits from bots diff --git a/src/short-urls/helpers/ShortUrlsRow.tsx b/src/short-urls/helpers/ShortUrlsRow.tsx index 5e3d4748..ebc5a968 100644 --- a/src/short-urls/helpers/ShortUrlsRow.tsx +++ b/src/short-urls/helpers/ShortUrlsRow.tsx @@ -87,7 +87,7 @@ export const ShortUrlsRow = ( - + diff --git a/src/short-urls/helpers/ShortUrlsRowMenu.tsx b/src/short-urls/helpers/ShortUrlsRowMenu.tsx index 7b08fe89..c7a20b35 100644 --- a/src/short-urls/helpers/ShortUrlsRowMenu.tsx +++ b/src/short-urls/helpers/ShortUrlsRowMenu.tsx @@ -8,8 +8,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import type { FC } from 'react'; import { DropdownItem } from 'reactstrap'; import type { SelectedServer } from '../../servers/data'; -import { DropdownBtnMenu } from '../../utils/DropdownBtnMenu'; import { useToggle } from '../../utils/helpers/hooks'; +import { RowDropdownBtn } from '../../utils/RowDropdownBtn'; import type { ShortUrl, ShortUrlModalProps } from '../data'; import { ShortUrlDetailLink } from './ShortUrlDetailLink'; @@ -23,12 +23,11 @@ export const ShortUrlsRowMenu = ( DeleteShortUrlModal: ShortUrlModal, QrCodeModal: ShortUrlModal, ) => ({ shortUrl, selectedServer }: ShortUrlsRowMenuProps) => { - const [isOpen, toggle] = useToggle(); const [isQrModalOpen,, openQrCodeModal, closeQrCodeModal] = useToggle(); const [isDeleteModalOpen,, openDeleteModal, closeDeleteModal] = useToggle(); return ( - + Visit stats @@ -48,7 +47,7 @@ export const ShortUrlsRowMenu = ( Delete short URL - + ); }; diff --git a/src/tags/TagsTableRow.tsx b/src/tags/TagsTableRow.tsx index d3257bae..b643a388 100644 --- a/src/tags/TagsTableRow.tsx +++ b/src/tags/TagsTableRow.tsx @@ -5,9 +5,9 @@ import { Link } from 'react-router-dom'; import { DropdownItem } from 'reactstrap'; import type { SelectedServer } from '../servers/data'; import { getServerId } from '../servers/data'; -import { DropdownBtnMenu } from '../utils/DropdownBtnMenu'; import { useToggle } from '../utils/helpers/hooks'; import { prettify } from '../utils/helpers/numbers'; +import { RowDropdownBtn } from '../utils/RowDropdownBtn'; import type { ColorGenerator } from '../utils/services/ColorGenerator'; import type { SimplifiedTag, TagModalProps } from './data'; import { TagBullet } from './helpers/TagBullet'; @@ -24,7 +24,6 @@ export const TagsTableRow = ( ) => ({ tag, selectedServer }: TagsTableRowProps) => { const [isDeleteModalOpen, toggleDelete] = useToggle(); const [isEditModalOpen, toggleEdit] = useToggle(); - const [isDropdownOpen, toggleDropdown] = useToggle(); const serverId = getServerId(selectedServer); return ( @@ -43,14 +42,14 @@ export const TagsTableRow = ( - + Edit Delete - + diff --git a/src/utils/DropdownBtn.scss b/src/utils/DropdownBtn.scss index 451a2346..86395a84 100644 --- a/src/utils/DropdownBtn.scss +++ b/src/utils/DropdownBtn.scss @@ -4,6 +4,9 @@ .dropdown-btn__toggle.dropdown-btn__toggle { text-align: left; +} + +.dropdown-btn__toggle.dropdown-btn__toggle--with-caret { padding-right: 1.75rem; } diff --git a/src/utils/DropdownBtn.tsx b/src/utils/DropdownBtn.tsx index 46e69421..c6076a86 100644 --- a/src/utils/DropdownBtn.tsx +++ b/src/utils/DropdownBtn.tsx @@ -1,30 +1,45 @@ import classNames from 'classnames'; -import type { FC, PropsWithChildren } from 'react'; +import type { FC, PropsWithChildren, ReactNode } from 'react'; import { Dropdown, DropdownMenu, DropdownToggle } from 'reactstrap'; +import type { DropdownToggleProps } from 'reactstrap/types/lib/DropdownToggle'; import { useToggle } from './helpers/hooks'; import './DropdownBtn.scss'; -export type DropdownBtnProps = PropsWithChildren<{ - text: string; - disabled?: boolean; +export type DropdownBtnProps = PropsWithChildren & { + text: ReactNode; + noCaret?: boolean; className?: string; dropdownClassName?: string; - right?: boolean; inline?: boolean; minWidth?: number; + size?: 'sm' | 'md' | 'lg'; }>; -export const DropdownBtn: FC = ( - { text, disabled = false, className, children, dropdownClassName, right = false, minWidth, inline }, -) => { +export const DropdownBtn: FC = ({ + text, + disabled = false, + className, + children, + dropdownClassName, + noCaret, + end = false, + minWidth, + inline, + size, +}) => { const [isOpen, toggle] = useToggle(); - const toggleClasses = classNames('dropdown-btn__toggle', className, { 'btn-block': !inline }); - const style = { minWidth: minWidth && `${minWidth}px` }; + const toggleClasses = classNames('dropdown-btn__toggle', className, { + 'btn-block': !inline, + 'dropdown-btn__toggle--with-caret': !noCaret, + }); + const menuStyle = { minWidth: minWidth && `${minWidth}px` }; return ( - {text} - {children} + + {text} + + {children} ); }; diff --git a/src/utils/DropdownBtnMenu.scss b/src/utils/DropdownBtnMenu.scss deleted file mode 100644 index 45b54a28..00000000 --- a/src/utils/DropdownBtnMenu.scss +++ /dev/null @@ -1,3 +0,0 @@ -.dropdown-btn-menu__dropdown-toggle:after { - display: none !important; -} diff --git a/src/utils/DropdownBtnMenu.tsx b/src/utils/DropdownBtnMenu.tsx deleted file mode 100644 index 330c5434..00000000 --- a/src/utils/DropdownBtnMenu.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { faEllipsisV as menuIcon } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import type { FC, PropsWithChildren } from 'react'; -import { ButtonDropdown, DropdownMenu, DropdownToggle } from 'reactstrap'; -import './DropdownBtnMenu.scss'; - -export type DropdownBtnMenuProps = PropsWithChildren<{ - isOpen: boolean; - toggle: () => void; - right?: boolean; -}>; - -export const DropdownBtnMenu: FC = ({ isOpen, toggle, children, right = true }) => ( - - -    - - {children} - -); diff --git a/src/utils/RowDropdownBtn.tsx b/src/utils/RowDropdownBtn.tsx new file mode 100644 index 00000000..8225386d --- /dev/null +++ b/src/utils/RowDropdownBtn.tsx @@ -0,0 +1,21 @@ +import { faEllipsisV as menuIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import type { FC, PropsWithChildren } from 'react'; +import { DropdownBtn } from './DropdownBtn'; + +export type DropdownBtnMenuProps = PropsWithChildren<{ + minWidth?: number; +}>; + +export const RowDropdownBtn: FC = ({ children, minWidth }) => ( + } + size="sm" + minWidth={minWidth} + end + noCaret + inline + > + {children} + +); diff --git a/src/visits/helpers/VisitsFilterDropdown.tsx b/src/visits/helpers/VisitsFilterDropdown.tsx index 8abb66b4..7a901421 100644 --- a/src/visits/helpers/VisitsFilterDropdown.tsx +++ b/src/visits/helpers/VisitsFilterDropdown.tsx @@ -22,7 +22,7 @@ export const VisitsFilterDropdown = ( const onBotsClick = () => onChange({ ...selected, excludeBots: !selected?.excludeBots }); return ( - + Bots: Exclude potential bots diff --git a/test/domains/DomainSelector.test.tsx b/test/domains/DomainSelector.test.tsx index 008414af..de324e41 100644 --- a/test/domains/DomainSelector.test.tsx +++ b/test/domains/DomainSelector.test.tsx @@ -26,9 +26,8 @@ describe('', () => { const btn = screen.getByRole('button', { name: expectedText }); expect(screen.queryByPlaceholderText('Domain')).not.toBeInTheDocument(); - expect(btn).toHaveAttribute( - 'class', - `dropdown-btn__toggle btn-block ${expectedClassName} dropdown-toggle btn btn-primary`, + expect(btn).toHaveClass( + `dropdown-btn__toggle ${expectedClassName} btn-block dropdown-btn__toggle--with-caret dropdown-toggle btn btn-primary`, ); await user.click(btn); diff --git a/test/utils/DropdownBtnMenu.test.tsx b/test/utils/DropdownBtnMenu.test.tsx deleted file mode 100644 index 22741fde..00000000 --- a/test/utils/DropdownBtnMenu.test.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { screen } from '@testing-library/react'; -import { fromPartial } from '@total-typescript/shoehorn'; -import type { DropdownBtnMenuProps } from '../../src/utils/DropdownBtnMenu'; -import { DropdownBtnMenu } from '../../src/utils/DropdownBtnMenu'; -import { renderWithEvents } from '../__helpers__/setUpTest'; - -describe('', () => { - const setUp = (props: Partial = {}) => renderWithEvents( - ({ toggle: jest.fn(), ...props })}> - the children - , - ); - - it('renders expected components', () => { - setUp(); - const toggle = screen.getByRole('button'); - - expect(toggle).toBeInTheDocument(); - expect(toggle).toHaveClass('btn-sm'); - expect(toggle).toHaveClass('dropdown-btn-menu__dropdown-toggle'); - expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument(); - }); - - it('renders expected children', () => { - setUp(); - expect(screen.getByText('the children')).toBeInTheDocument(); - }); - - it.each([ - [undefined, true], - [true, true], - [false, false], - ])('renders menu to the end when expected', (right, expectedEnd) => { - setUp({ right }); - - if (expectedEnd) { - expect(screen.getByRole('menu', { hidden: true })).toHaveClass('dropdown-menu-end'); - } else { - expect(screen.getByRole('menu', { hidden: true })).not.toHaveClass('dropdown-menu-end'); - } - }); -}); diff --git a/test/utils/RowDropdownBtn.test.tsx b/test/utils/RowDropdownBtn.test.tsx new file mode 100644 index 00000000..ae47a0ce --- /dev/null +++ b/test/utils/RowDropdownBtn.test.tsx @@ -0,0 +1,28 @@ +import { screen } from '@testing-library/react'; +import { fromPartial } from '@total-typescript/shoehorn'; +import type { DropdownBtnMenuProps } from '../../src/utils/RowDropdownBtn'; +import { RowDropdownBtn } from '../../src/utils/RowDropdownBtn'; +import { renderWithEvents } from '../__helpers__/setUpTest'; + +describe('', () => { + const setUp = (props: Partial = {}) => renderWithEvents( + ({ ...props })}> + the children + , + ); + + it('renders expected components', () => { + setUp(); + const toggle = screen.getByRole('button'); + + expect(toggle).toBeInTheDocument(); + expect(toggle).toHaveClass('btn-sm'); + expect(toggle).toHaveClass('dropdown-btn__toggle'); + expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument(); + }); + + it('renders expected children', () => { + setUp(); + expect(screen.getByText('the children')).toBeInTheDocument(); + }); +});