Skip to content

Commit

Permalink
feat(react): add toggletip component (#10365)
Browse files Browse the repository at this point in the history
* feat(react): add toggletip component

* chore: check-in work

* feat(react): add toggletip component

* chore: revert icon button changes and update custom property usage

* Update packages/react/src/components/Toggletip/index.js

Co-authored-by: Taylor Jones <[email protected]>

* feat(styles): update link tokens, toggletip styles

* feat(button,toggletip): add button-focus-color component token

Co-authored-by: Taylor Jones <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 25, 2022
1 parent a7112be commit 7ef288a
Show file tree
Hide file tree
Showing 14 changed files with 618 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

describe('Toggletip', () => {
describe('accessibility', () => {
test.todo('accessibility-checker');
test.todo('axe');
});

// Usage
it.todo('should toggle visibility on click');
it.todo('should toggle visibility on Enter and Space');
it.todo('should close on Escape');
it.todo('should close if an element outside of the toggletip is clicked');

describe('Component API', () => {
it.todo('should support custom elements with the `as` prop');
it.todo('should support a custom class name with the `className` prop');
it.todo('should support different alignments with the `align` prop');
it.todo('should initially be open if `defaultOpen` is set to true');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

describe('ToggletipActions', () => {
describe('Component API', () => {
it.todo('should support a custom class name with the `className` prop');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

describe('ToggletipButton', () => {
describe('Component API', () => {
it.todo('should support a custom class name with the `className` prop');
it.todo('should use the `label` prop as the label for the <button>');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

describe('ToggletipContent', () => {
describe('Component API', () => {
it.todo('should support a custom class name with the `className` prop');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

describe('ToggletipLabel', () => {
describe('Component API', () => {
it.todo('should support custom elements with the `as` prop');
it.todo('should support a custom class name with the `className` prop');
});
});
252 changes: 252 additions & 0 deletions packages/react/src/components/Toggletip/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import cx from 'classnames';
import PropTypes from 'prop-types';
import React, { useContext, useRef, useState } from 'react';
import { Popover, PopoverContent } from '../Popover';
import { match, keys } from '../../internal/keyboard';
import { useWindowEvent } from '../../internal/useEvent';
import { useId } from '../../internal/useId';
import { usePrefix } from '../../internal/usePrefix';

/**
* Used to render the label for a Toggletip
*/
function ToggletipLabel({
as: BaseComponent = 'span',
children,
className: customClassName,
}) {
const prefix = usePrefix();
const className = cx(`${prefix}--toggletip-label`, customClassName);
return <BaseComponent className={className}>{children}</BaseComponent>;
}

ToggletipLabel.propTypes = {
/**
* Provide a custom element or component to render the top-level node for the
* component.
*/
as: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType]),

/**
* Custom children to be rendered as the content of the label
*/
children: PropTypes.node,

/**
* Provide a custom class name to be added to the outermost node in the
* component
*/
className: PropTypes.string,
};

// Used to coordinate accessibility props between button and content along with
// the actions to open and close the toggletip
const ToggletipContext = React.createContext();

function useToggletip() {
return useContext(ToggletipContext);
}

/**
* Used as a container for the button and content of a toggletip. This component
* is responsible for coordinating between interactions with the button and the
* visibility of the content
*/
function Toggletip({
align,
as,
className: customClassName,
children,
defaultOpen = false,
}) {
const ref = useRef();
const [open, setOpen] = useState(defaultOpen);
const prefix = usePrefix();
const id = useId();
const className = cx(`${prefix}--toggletip`, customClassName, {
[`${prefix}--toggletip--open`]: open,
});
const actions = {
toggle: () => {
setOpen(!open);
},
close: () => {
setOpen(false);
},
};
const value = {
buttonProps: {
'aria-expanded': open,
'aria-controls': id,
onClick: actions.toggle,
},
contentProps: {
id,
},
};

function onKeyDown(event) {
if (open && match(event, keys.Escape)) {
actions.close();
}
}

useWindowEvent('click', (event) => {
if (open && !ref.current.contains(event.target)) {
actions.close();
}
});

return (
<ToggletipContext.Provider value={value}>
<Popover
align={align}
as={as}
caret
className={className}
dropShadow={false}
highContrast
open={open}
onKeyDown={onKeyDown}
ref={ref}>
{children}
</Popover>
</ToggletipContext.Provider>
);
}

Toggletip.propTypes = {
/**
* Specify how the toggletip should align with the button
*/
align: PropTypes.oneOf(['top', 'bottom', 'left', 'right']),

/**
* Provide a custom element or component to render the top-level node for the
* component.
*/
as: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType]),

/**
* Custom children to be rendered as the content of the label
*/
children: PropTypes.node,

/**
* Provide a custom class name to be added to the outermost node in the
* component
*/
className: PropTypes.string,

/**
* Specify if the toggletip should be open by default
*/
defaultOpen: PropTypes.bool,
};

/**
* `ToggletipButton` controls the visibility of the Toggletip through mouse
* clicks and keyboard interactions.
*/
function ToggletipButton({
children,
className: customClassName,
label = 'Show information',
}) {
const toggletip = useToggletip();
const prefix = usePrefix();
const className = cx(`${prefix}--toggletip-button`, customClassName);
return (
<button
{...toggletip.buttonProps}
aria-label={label}
type="button"
className={className}>
{children}
</button>
);
}

ToggletipButton.propTypes = {
/**
* Custom children to be rendered as the content of the label
*/
children: PropTypes.node,

/**
* Provide a custom class name to be added to the outermost node in the
* component
*/
className: PropTypes.string,

/**
* Provide an accessible label for this button
*/
label: PropTypes.string,
};

/**
* `ToggletipContent` is a wrapper around `PopoverContent`. It places the
* `children` passed in as a prop inside of `PopoverContent` so that they will
* be rendered inside of the popover for this component.
*/
function ToggletipContent({ children, className: customClassName }) {
const toggletip = useToggletip();
const prefix = usePrefix();
return (
<PopoverContent className={customClassName} {...toggletip.contentProps}>
<div className={`${prefix}--toggletip-content`}>{children}</div>
</PopoverContent>
);
}

ToggletipContent.propTypes = {
/**
* Custom children to be rendered as the content of the label
*/
children: PropTypes.node,

/**
* Provide a custom class name to be added to the outermost node in the
* component
*/
className: PropTypes.string,
};

/**
* `ToggletipActions` is a container for one or two actions present at the base
* of a toggletip. It is used for layout of these items.
*/
function ToggletipActions({ children, className: customClassName }) {
const prefix = usePrefix();
const className = cx(`${prefix}--toggletip-actions`, customClassName);
return <div className={className}>{children}</div>;
}

ToggletipActions.propTypes = {
/**
* Custom children to be rendered as the content of the label
*/
children: PropTypes.node,

/**
* Provide a custom class name to be added to the outermost node in the
* component
*/
className: PropTypes.string,
};

export {
ToggletipLabel,
Toggletip,
ToggletipButton,
ToggletipContent,
ToggletipActions,
};
29 changes: 29 additions & 0 deletions packages/react/src/components/Toggletip/next/Toggletip.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Preview, Props, Story } from '@storybook/addon-docs/blocks';

# Toggletip

[Source code](https://github.com/carbon-design-system/carbon/tree/main/packages/react/src/components/Toggletip)

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

## Table of Contents

- [Overview](#overview)
- [Component API](#component-api)
- [Feedback](#feedback)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Overview

## Component API

<Props />

## Feedback

Help us improve this component by providing feedback, asking questions on Slack,
or updating this file on
[GitHub](https://github.com/carbon-design-system/carbon/edit/main/packages/react/src/components/Toggletip/next/Toggletip.mdx).

Loading

0 comments on commit 7ef288a

Please sign in to comment.