From b8711a031097a824d4df8aec1d2de2625c2603e0 Mon Sep 17 00:00:00 2001 From: Andrew Brough Date: Tue, 29 Jul 2025 20:24:19 -0400 Subject: [PATCH 1/4] Dropdown.Toggle accepts all ButtonProps for styling and renders as a Button if button=true, or all LabelProps and a Label if button=false --- src/Dropdown/DropdownToggle.tsx | 70 +++++++++++++++------------------ 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/src/Dropdown/DropdownToggle.tsx b/src/Dropdown/DropdownToggle.tsx index abbef514..b8b1193b 100644 --- a/src/Dropdown/DropdownToggle.tsx +++ b/src/Dropdown/DropdownToggle.tsx @@ -1,48 +1,40 @@ -import React, { forwardRef } from 'react' +import React, { forwardRef, LabelHTMLAttributes } from 'react' +import Button, { ButtonProps } from '../Button' +import Label, { LabelProps } from '../Form/Label' -import { ComponentColor, ComponentSize, IComponentBaseProps } from '../types' +export type ButtonDropdownToggleProps = ButtonProps & { + button?: true +} -import Button, { ButtonProps } from '../Button' +export type LabelDropdownToggleProps = LabelProps & { + button?: false +} -export type DropdownToggleProps = Omit< - React.LabelHTMLAttributes, - 'color' -> & - IComponentBaseProps & { - color?: ComponentColor - size?: ComponentSize - button?: boolean - disabled?: boolean - } +export type DropdownToggleProps = React.HTMLAttributes & + (ButtonDropdownToggleProps | LabelDropdownToggleProps) -const DropdownToggle = ({ - children, - color, - size, - button = true, - dataTheme, - className, - disabled, - ...props -}: DropdownToggleProps) => { - return ( - - ) -} + ) + } else { + return ( + + ) + } + } +) export type SummaryProps = Omit export const Summary = forwardRef( From 9da8e4f6f5cf1ecf5a11ab78b25688fcb3302b84 Mon Sep 17 00:00:00 2001 From: Andrew Brough Date: Tue, 29 Jul 2025 20:29:04 -0400 Subject: [PATCH 2/4] add unstyled prop for Dropdown.Toggle which renders as a div if unstyled=true --- src/Dropdown/DropdownToggle.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Dropdown/DropdownToggle.tsx b/src/Dropdown/DropdownToggle.tsx index b8b1193b..d7a93809 100644 --- a/src/Dropdown/DropdownToggle.tsx +++ b/src/Dropdown/DropdownToggle.tsx @@ -1,21 +1,34 @@ -import React, { forwardRef, LabelHTMLAttributes } from 'react' +import React, { forwardRef, HTMLAttributes } from 'react' import Button, { ButtonProps } from '../Button' import Label, { LabelProps } from '../Form/Label' export type ButtonDropdownToggleProps = ButtonProps & { button?: true + unstyled?: false } export type LabelDropdownToggleProps = LabelProps & { button?: false + unstyled?: false } -export type DropdownToggleProps = React.HTMLAttributes & +export type DropdownToggleProps = HTMLAttributes & (ButtonDropdownToggleProps | LabelDropdownToggleProps) const DropdownToggle = forwardRef( (props, ref) => { - const { button = true, children, ...rest } = props + const { button = true, unstyled = false, children, ...rest } = props + + if (unstyled) { + return ( +
} + {...(rest as React.HTMLAttributes)} + > + {children} +
+ ) + } if (button) { return ( From 921a2ecb93b632ba302ff0fcfad23c01e65ca85d Mon Sep 17 00:00:00 2001 From: Andrew Brough Date: Tue, 29 Jul 2025 23:59:15 -0400 Subject: [PATCH 3/4] Fix type autocomplete issues due to conflicting types, add role="button" to each tag for accessibility and testing, update existing and add tests for tag rendering (button, label, div) --- src/Dropdown/Dropdown.stories.tsx | 49 +++++++++++++++++++++++++++ src/Dropdown/Dropdown.test.tsx | 56 ++++++++++++++++++++++++++++++- src/Dropdown/DropdownToggle.tsx | 41 +++++++++++----------- 3 files changed, 125 insertions(+), 21 deletions(-) diff --git a/src/Dropdown/Dropdown.stories.tsx b/src/Dropdown/Dropdown.stories.tsx index f226064e..9b833e9e 100644 --- a/src/Dropdown/Dropdown.stories.tsx +++ b/src/Dropdown/Dropdown.stories.tsx @@ -5,6 +5,7 @@ import Dropdown, { DropdownProps } from '.' import Card from '../Card/' import Navbar from '../Navbar' import Button from '../Button' +import Badge from '../Badge' export default { title: 'Actions/Dropdown', @@ -76,6 +77,54 @@ InNavbar.args = { end: true, } +export const ButtonToggle: Story = (args) => { + return ( +
+ + + Click + + + Item 1 + Item 2 + + +
+ ) +} + +export const LabelToggle: Story = (args) => { + return ( +
+ + Click + + Item 1 + Item 2 + + +
+ ) +} + +export const UnstyledCustomToggle: Story = (args) => { + return ( +
+ + + + Any component can be a toggle, even a {``}! + + + + Item 1 + Item 2 + + +
+ ) +} + export const Helper: Story = (args) => { return (
diff --git a/src/Dropdown/Dropdown.test.tsx b/src/Dropdown/Dropdown.test.tsx index bce2631b..e8c71a7d 100644 --- a/src/Dropdown/Dropdown.test.tsx +++ b/src/Dropdown/Dropdown.test.tsx @@ -34,7 +34,7 @@ describe('Dropdown', () => { ) expect(screen.getByRole('listbox')).toHaveClass('custom-dropdown') - expect(screen.getByText('Toggle').parentElement).toHaveClass( + expect(screen.getByText('Toggle').closest('button')).toHaveClass( 'custom-toggle' ) expect(screen.getByRole('menu')).toHaveClass('custom-menu') @@ -75,4 +75,58 @@ describe('Dropdown', () => { expect(screen.getByRole('listbox')).toHaveClass('dropdown-hover') expect(screen.getByRole('listbox')).toHaveClass('dropdown-open') }) + + test('Should render a Button when button=true (default)', () => { + render( + + Toggle + {DropdownItems} + + ) + + const toggle = screen.getByRole('button', { name: 'Toggle' }) + expect(toggle).toBeInTheDocument() + expect(toggle.tagName).toBe('BUTTON') + }) + + test('Should render a Button when button=true (explicit)', () => { + render( + + Toggle + {DropdownItems} + + ) + + const toggle = screen.getByRole('button', { name: 'Toggle' }) + expect(toggle).toBeInTheDocument() + expect(toggle.tagName).toBe('BUTTON') + }) + + test('Should render a Label when button=false', () => { + render( + + Toggle + {DropdownItems} + + ) + + const toggle = screen.getByRole('button', { name: 'Toggle' }) + expect(toggle).toBeInTheDocument() + expect(toggle.tagName).toBe('LABEL') + }) + + test('Should render a div when unstyled=true', () => { + render( + + + Toggle + + {DropdownItems} + + ) + + const toggle = screen.getByRole('button', { name: 'Dropdown toggle' }) + expect(toggle).toBeInTheDocument() + expect(toggle.tagName).toBe('DIV') + }) }) diff --git a/src/Dropdown/DropdownToggle.tsx b/src/Dropdown/DropdownToggle.tsx index d7a93809..5ed17f36 100644 --- a/src/Dropdown/DropdownToggle.tsx +++ b/src/Dropdown/DropdownToggle.tsx @@ -7,43 +7,44 @@ export type ButtonDropdownToggleProps = ButtonProps & { unstyled?: false } -export type LabelDropdownToggleProps = LabelProps & { - button?: false +export type LabelDropdownToggleProps = Omit & { + button: false unstyled?: false } -export type DropdownToggleProps = HTMLAttributes & - (ButtonDropdownToggleProps | LabelDropdownToggleProps) +export type UnstyledDropdownToggleProps = HTMLAttributes & { + button?: false + unstyled: true +} + +export type DropdownToggleProps = + | ButtonDropdownToggleProps + | LabelDropdownToggleProps + | UnstyledDropdownToggleProps const DropdownToggle = forwardRef( (props, ref) => { - const { button = true, unstyled = false, children, ...rest } = props + const { button = true, unstyled = false, ...rest } = props if (unstyled) { return (
} + role="button" + tabIndex={0} {...(rest as React.HTMLAttributes)} - > - {children} -
- ) - } - - if (button) { - return ( - + /> ) + } else if (button) { + return