-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'gpn' of https://github.com/hectorjjb/fluentui-contrib i…
…nto gpn
- Loading branch information
Showing
20 changed files
with
648 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
packages/teams-components/src/components/Button/Button.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import * as React from 'react'; | ||
import { fireEvent, render, screen, act } from '@testing-library/react'; | ||
import { Menu, MenuPopover, MenuTrigger } from '@fluentui/react-components'; | ||
import { Button } from './Button'; | ||
|
||
describe('Button', () => { | ||
beforeEach(() => { | ||
console.error = jest.fn(); | ||
}); | ||
|
||
it('should throw error for icon button if no title or aria-label is provided', () => { | ||
expect(() => render(<Button icon={<i>X</i>} />)).toThrow( | ||
'Icon button must have a title' | ||
); | ||
}); | ||
|
||
it('should not throw error for icon button if aria-label is provided', () => { | ||
console.error = jest.fn(); | ||
expect(() => | ||
render(<Button aria-label="label" icon={<i>X</i>} />) | ||
).not.toThrow(); | ||
}); | ||
|
||
it('should render title', () => { | ||
jest.useFakeTimers(); | ||
const { getByRole } = render(<Button icon={<i>X</i>} title={'Tooltip'} />); | ||
|
||
const button = getByRole('button'); | ||
fireEvent.pointerEnter(button); | ||
act(() => { | ||
jest.runOnlyPendingTimers(); | ||
}); | ||
|
||
const title = screen.queryByText('Tooltip'); | ||
expect(title).not.toBeNull(); | ||
|
||
expect(title?.textContent).toEqual(button.getAttribute('aria-label')); | ||
}); | ||
|
||
it('should error when attempting to wrap with a menu', () => { | ||
expect(() => | ||
render( | ||
<Menu> | ||
<MenuTrigger> | ||
<Button icon={<i>X</i>} /> | ||
</MenuTrigger> | ||
<MenuPopover></MenuPopover> | ||
</Menu> | ||
) | ||
).toThrow('Icon button must have a title'); | ||
}); | ||
}); |
66 changes: 66 additions & 0 deletions
66
packages/teams-components/src/components/Button/Button.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import * as React from 'react'; | ||
import { | ||
useButton_unstable, | ||
useButtonStyles_unstable, | ||
renderButton_unstable, | ||
type ButtonProps as ButtonPropsBase, | ||
Tooltip, | ||
} from '@fluentui/react-components'; | ||
import { validateIconButton, validateMenuButton } from './validateProps'; | ||
import { type StrictCssClass, validateStrictClasses } from '../../strictStyles'; | ||
import { type StrictSlot } from '../../strictSlot'; | ||
|
||
export interface ButtonProps | ||
extends Pick< | ||
ButtonPropsBase, | ||
| 'aria-label' | ||
| 'aria-labelledby' | ||
| 'aria-describedby' | ||
| 'size' | ||
| 'children' | ||
| 'disabled' | ||
| 'disabledFocusable' | ||
> { | ||
appearance?: 'transparent' | 'primary'; | ||
className?: StrictCssClass; | ||
icon?: StrictSlot; | ||
onClick?: React.MouseEventHandler<HTMLButtonElement>; | ||
title?: StrictSlot; | ||
} | ||
|
||
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( | ||
(userProps, ref) => { | ||
if (process.env.NODE_ENV !== 'production') { | ||
validateProps(userProps); | ||
} | ||
|
||
const { className, icon, title, ...restProps } = userProps; | ||
const props: ButtonPropsBase = { | ||
...restProps, | ||
className: className?.toString(), | ||
iconPosition: 'before', | ||
icon, | ||
}; | ||
|
||
let state = useButton_unstable(props, ref); | ||
state = useButtonStyles_unstable(state); | ||
|
||
const button = renderButton_unstable(state); | ||
|
||
if (title) { | ||
return ( | ||
<Tooltip content={title} relationship="label"> | ||
{button} | ||
</Tooltip> | ||
); | ||
} | ||
|
||
return button; | ||
} | ||
); | ||
|
||
const validateProps = (props: ButtonProps) => { | ||
validateStrictClasses(props.className); | ||
validateIconButton(props); | ||
validateMenuButton(props); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './Button'; | ||
export * from './validateProps'; |
38 changes: 38 additions & 0 deletions
38
packages/teams-components/src/components/Button/validateProps.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/** | ||
* @throws Error if icon button is missing required props | ||
*/ | ||
export const validateIconButton = (props: { | ||
icon?: unknown; | ||
children?: unknown; | ||
title?: unknown; | ||
'aria-label'?: string; | ||
'aria-labelledby'?: string; | ||
}) => { | ||
if ( | ||
!props.children && | ||
props.icon && | ||
!props.title && | ||
!(props['aria-label'] || props['aria-labelledby']) | ||
) { | ||
throw new Error( | ||
'@fluentui-contrib/teams-components::Icon button must have a title or aria label' | ||
); | ||
} | ||
}; | ||
|
||
/** | ||
* Infers a Menu being used by detecting `aria-haspopup` of the MenuTrigger | ||
* @throws Error if a menu is used | ||
*/ | ||
export const validateMenuButton = (props: unknown) => { | ||
if ( | ||
typeof props === 'object' && | ||
props && | ||
'aria-haspopup' in props && | ||
props['aria-haspopup'] === 'menu' | ||
) { | ||
throw new Error( | ||
'@fluentui-contrib/teams-components:: MenuButton should be used to open a Menu' | ||
); | ||
} | ||
}; |
22 changes: 22 additions & 0 deletions
22
packages/teams-components/src/components/MenuButton/MenuButton.styles.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { makeStyles, shorthands, tokens } from '@fluentui/react-components'; | ||
|
||
export const useStyles = makeStyles({ | ||
root: { | ||
...shorthands.padding(tokens.spacingHorizontalM), | ||
...shorthands.border( | ||
tokens.strokeWidthThin, | ||
'solid', | ||
tokens.colorNeutralStroke1 | ||
), | ||
color: tokens.colorNeutralForeground1, | ||
backgroundColor: tokens.colorNeutralBackground1, | ||
display: 'flex', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
maxWidth: '200px', | ||
':hover': { | ||
backgroundColor: tokens.colorNeutralBackground1Hover, | ||
color: tokens.colorNeutralForeground1Hover, | ||
}, | ||
}, | ||
}); |
9 changes: 9 additions & 0 deletions
9
packages/teams-components/src/components/MenuButton/MenuButton.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import * as React from 'react'; | ||
import { render } from '@testing-library/react'; | ||
import { MenuButton } from './MenuButton'; | ||
|
||
describe('MenuButton', () => { | ||
it('should render', () => { | ||
render(<MenuButton />); | ||
}); | ||
}); |
45 changes: 45 additions & 0 deletions
45
packages/teams-components/src/components/MenuButton/MenuButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import * as React from 'react'; | ||
import { | ||
useMenuButton_unstable, | ||
useMenuButtonStyles_unstable, | ||
renderMenuButton_unstable, | ||
type MenuButtonProps as MenuButtonPropsBase, | ||
Tooltip, | ||
} from '@fluentui/react-components'; | ||
import { ButtonProps, validateIconButton } from '../Button'; | ||
import { validateStrictClasses } from '../../strictStyles'; | ||
|
||
export const MenuButton = React.forwardRef<HTMLButtonElement, ButtonProps>( | ||
(userProps, ref) => { | ||
if (process.env.NODE_ENV !== 'production') { | ||
validateProps(userProps); | ||
} | ||
|
||
const { className, icon, title, ...restProps } = userProps; | ||
const props: MenuButtonPropsBase = { | ||
...restProps, | ||
className: className?.toString(), | ||
icon, | ||
}; | ||
|
||
let state = useMenuButton_unstable(props, ref); | ||
state = useMenuButtonStyles_unstable(state); | ||
|
||
const button = renderMenuButton_unstable(state); | ||
|
||
if (title) { | ||
return ( | ||
<Tooltip content={title} relationship="label"> | ||
{button} | ||
</Tooltip> | ||
); | ||
} | ||
|
||
return button; | ||
} | ||
); | ||
|
||
const validateProps = (props: ButtonProps) => { | ||
validateStrictClasses(props.className); | ||
validateIconButton(props); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './MenuButton'; |
55 changes: 55 additions & 0 deletions
55
packages/teams-components/src/components/ToggleButton/ToggleButton.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import * as React from 'react'; | ||
import { fireEvent, render, screen, act } from '@testing-library/react'; | ||
import { Menu, MenuPopover, MenuTrigger } from '@fluentui/react-components'; | ||
import { ToggleButton } from './ToggleButton'; | ||
|
||
describe('ToggleButton', () => { | ||
beforeEach(() => { | ||
console.error = jest.fn(); | ||
}); | ||
|
||
it('should throw error for icon button if no title or aria-label is provided', () => { | ||
console.error = jest.fn(); | ||
expect(() => render(<ToggleButton checked icon={<i>X</i>} />)).toThrow( | ||
'Icon button must have a title' | ||
); | ||
}); | ||
|
||
it('should not throw error for icon button if aria-label is provided', () => { | ||
console.error = jest.fn(); | ||
expect(() => | ||
render(<ToggleButton checked aria-label="label" icon={<i>X</i>} />) | ||
).not.toThrow(); | ||
}); | ||
|
||
it('should render title', () => { | ||
jest.useFakeTimers(); | ||
const { getByRole } = render( | ||
<ToggleButton checked icon={<i>X</i>} title={'Tooltip'} /> | ||
); | ||
|
||
const button = getByRole('button'); | ||
fireEvent.pointerEnter(button); | ||
act(() => { | ||
jest.runOnlyPendingTimers(); | ||
}); | ||
|
||
const title = screen.queryByText('Tooltip'); | ||
expect(title).not.toBeNull(); | ||
|
||
expect(title?.textContent).toEqual(button.getAttribute('aria-label')); | ||
}); | ||
|
||
it('should error when attempting to wrap with a menu', () => { | ||
expect(() => | ||
render( | ||
<Menu> | ||
<MenuTrigger> | ||
<ToggleButton checked={false} icon={<i>X</i>} /> | ||
</MenuTrigger> | ||
<MenuPopover></MenuPopover> | ||
</Menu> | ||
) | ||
).toThrow('Icon button must have a title'); | ||
}); | ||
}); |
52 changes: 52 additions & 0 deletions
52
packages/teams-components/src/components/ToggleButton/ToggleButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import * as React from 'react'; | ||
import { | ||
type ToggleButtonProps as ToggleButtonPropsBase, | ||
useToggleButtonStyles_unstable, | ||
useToggleButton_unstable, | ||
renderToggleButton_unstable, | ||
Tooltip, | ||
} from '@fluentui/react-components'; | ||
import { validateStrictClasses } from '../../strictStyles'; | ||
import { ButtonProps, validateIconButton, validateMenuButton } from '../Button'; | ||
|
||
export interface ToggleButtonProps extends ButtonProps { | ||
checked: boolean; | ||
} | ||
|
||
export const ToggleButton = React.forwardRef< | ||
HTMLButtonElement, | ||
ToggleButtonProps | ||
>((userProps, ref) => { | ||
if (process.env.NODE_ENV !== 'production') { | ||
validateProps(userProps); | ||
} | ||
|
||
const { className, icon, title, ...restProps } = userProps; | ||
const props: ToggleButtonPropsBase = { | ||
...restProps, | ||
className: className?.toString(), | ||
iconPosition: 'before', | ||
icon, | ||
}; | ||
|
||
let state = useToggleButton_unstable(props, ref); | ||
state = useToggleButtonStyles_unstable(state); | ||
|
||
const button = renderToggleButton_unstable(state); | ||
|
||
if (title) { | ||
return ( | ||
<Tooltip content={title} relationship="label"> | ||
{button} | ||
</Tooltip> | ||
); | ||
} | ||
|
||
return button; | ||
}); | ||
|
||
const validateProps = (props: ToggleButtonProps) => { | ||
validateStrictClasses(props.className); | ||
validateIconButton(props); | ||
validateMenuButton(props); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './ToggleButton'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,11 @@ | ||
export { MenuButton } from './components/MenuButton'; | ||
export { | ||
ToggleButton, | ||
type ToggleButtonProps, | ||
} from './components/ToggleButton'; | ||
export { | ||
makeStrictStyles, | ||
mergeStrictClasses, | ||
type StrictCssClass, | ||
} from './strictStyles'; | ||
export { Button, type ButtonProps } from './components/Button/Button'; |
Oops, something went wrong.