From 7ef288a07226059d583e3ea6752b4eb60b808d91 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Tue, 25 Jan 2022 15:30:40 -0600 Subject: [PATCH] feat(react): add toggletip component (#10365) * 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 * feat(styles): update link tokens, toggletip styles * feat(button,toggletip): add button-focus-color component token Co-authored-by: Taylor Jones Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Toggletip/__tests__/Toggletip-test.js | 26 ++ .../__tests__/ToggletipActions-test.js | 12 + .../Toggletip/__tests__/ToggletipButton.js | 13 + .../__tests__/ToggletipContent-test.js | 12 + .../__tests__/ToggletipLabel-test.js | 13 + .../react/src/components/Toggletip/index.js | 252 ++++++++++++++++++ .../components/Toggletip/next/Toggletip.mdx | 29 ++ .../Toggletip/next/Toggletip.stories.js | 123 +++++++++ packages/react/src/internal/useEvent.js | 27 +- packages/styles/scss/components/_index.scss | 1 + .../scss/components/button/_mixins.scss | 7 +- .../styles/scss/components/link/_link.scss | 17 +- .../scss/components/toggletip/_index.scss | 11 + .../scss/components/toggletip/_toggletip.scss | 81 ++++++ 14 files changed, 618 insertions(+), 6 deletions(-) create mode 100644 packages/react/src/components/Toggletip/__tests__/Toggletip-test.js create mode 100644 packages/react/src/components/Toggletip/__tests__/ToggletipActions-test.js create mode 100644 packages/react/src/components/Toggletip/__tests__/ToggletipButton.js create mode 100644 packages/react/src/components/Toggletip/__tests__/ToggletipContent-test.js create mode 100644 packages/react/src/components/Toggletip/__tests__/ToggletipLabel-test.js create mode 100644 packages/react/src/components/Toggletip/index.js create mode 100644 packages/react/src/components/Toggletip/next/Toggletip.mdx create mode 100644 packages/react/src/components/Toggletip/next/Toggletip.stories.js create mode 100644 packages/styles/scss/components/toggletip/_index.scss create mode 100644 packages/styles/scss/components/toggletip/_toggletip.scss diff --git a/packages/react/src/components/Toggletip/__tests__/Toggletip-test.js b/packages/react/src/components/Toggletip/__tests__/Toggletip-test.js new file mode 100644 index 000000000000..e6af109c5a9e --- /dev/null +++ b/packages/react/src/components/Toggletip/__tests__/Toggletip-test.js @@ -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'); + }); +}); diff --git a/packages/react/src/components/Toggletip/__tests__/ToggletipActions-test.js b/packages/react/src/components/Toggletip/__tests__/ToggletipActions-test.js new file mode 100644 index 000000000000..315a60eb64b4 --- /dev/null +++ b/packages/react/src/components/Toggletip/__tests__/ToggletipActions-test.js @@ -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'); + }); +}); diff --git a/packages/react/src/components/Toggletip/__tests__/ToggletipButton.js b/packages/react/src/components/Toggletip/__tests__/ToggletipButton.js new file mode 100644 index 000000000000..275805de040a --- /dev/null +++ b/packages/react/src/components/Toggletip/__tests__/ToggletipButton.js @@ -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 + ); +} + +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 ( + +
{children}
+
+ ); +} + +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
{children}
; +} + +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, +}; diff --git a/packages/react/src/components/Toggletip/next/Toggletip.mdx b/packages/react/src/components/Toggletip/next/Toggletip.mdx new file mode 100644 index 000000000000..2f12ca8115a3 --- /dev/null +++ b/packages/react/src/components/Toggletip/next/Toggletip.mdx @@ -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) + + + + +## Table of Contents + +- [Overview](#overview) +- [Component API](#component-api) +- [Feedback](#feedback) + + + +## Overview + +## Component API + + + +## 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). + diff --git a/packages/react/src/components/Toggletip/next/Toggletip.stories.js b/packages/react/src/components/Toggletip/next/Toggletip.stories.js new file mode 100644 index 000000000000..1a5f7bdc90da --- /dev/null +++ b/packages/react/src/components/Toggletip/next/Toggletip.stories.js @@ -0,0 +1,123 @@ +/** + * 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 { Information16 } from '@carbon/icons-react'; +import React from 'react'; +import { default as Button } from '../../Button'; +import { default as Link } from '../../Link'; +import { + ToggletipLabel, + Toggletip, + ToggletipButton, + ToggletipContent, + ToggletipActions, +} from '../../Toggletip'; +import mdx from './Toggletip.mdx'; + +export default { + title: 'Components/Toggletip', + component: Toggletip, + subcomponents: { + ToggletipLabel, + ToggletipButton, + ToggletipContent, + ToggletipActions, + }, + argTypes: { + as: { + table: { + disable: true, + }, + }, + children: { + table: { disable: true }, + }, + className: { + table: { + disable: true, + }, + }, + }, + parameters: { + docs: { + page: mdx, + }, + }, +}; + +export const Default = () => { + return ( +
+ Toggletip label + + + + + +

+ Lorem ipsum dolor sit amet, di os consectetur adipiscing elit, sed + do eiusmod tempor incididunt ut fsil labore et dolore magna aliqua. +

+ + Link action + + +
+
+
+ ); +}; + +const PlaygroundStory = (controls) => { + const { align } = controls; + return ( + <> + Toggletip label + + + + + +

+ Lorem ipsum dolor sit amet, di os consectetur adipiscing elit, sed + do eiusmod tempor incididunt ut fsil labore et dolore magna aliqua. +

+ + Link action + + +
+
+ + ); +}; + +export const Playground = PlaygroundStory.bind({}); + +Playground.argTypes = { + align: { + options: ['top', 'bottom', 'left', 'right'], + control: { + type: 'select', + }, + }, +}; + +Playground.story = { + decorators: [ + (story) => ( +
+ {story()} +
+ ), + ], +}; diff --git a/packages/react/src/internal/useEvent.js b/packages/react/src/internal/useEvent.js index 05e817433271..fe876c7a73d3 100644 --- a/packages/react/src/internal/useEvent.js +++ b/packages/react/src/internal/useEvent.js @@ -7,7 +7,7 @@ import { useEffect, useRef } from 'react'; -export function useEvent(element, eventName, callback) { +export function useEvent(elementOrRef, eventName, callback) { const savedCallback = useRef(null); useEffect(() => { @@ -21,10 +21,33 @@ export function useEvent(element, eventName, callback) { } } + const element = elementOrRef.current ?? elementOrRef; element.addEventListener(eventName, handler); return () => { element.removeEventListener(eventName, handler); }; - }, [element, eventName]); + }, [elementOrRef, eventName]); +} + +export function useWindowEvent(eventName, callback) { + const savedCallback = useRef(null); + + useEffect(() => { + savedCallback.current = callback; + }); + + useEffect(() => { + function handler(event) { + if (savedCallback.current) { + savedCallback.current(event); + } + } + + window.addEventListener(eventName, handler); + + return () => { + window.removeEventListener(eventName, handler); + }; + }, [eventName]); } diff --git a/packages/styles/scss/components/_index.scss b/packages/styles/scss/components/_index.scss index e9113da0e260..cca88e0e5936 100644 --- a/packages/styles/scss/components/_index.scss +++ b/packages/styles/scss/components/_index.scss @@ -51,6 +51,7 @@ @use 'text-input'; @use 'tile'; @use 'time-picker'; +@use 'toggletip'; @use 'toggle'; @use 'tooltip'; @use 'treeview'; diff --git a/packages/styles/scss/components/button/_mixins.scss b/packages/styles/scss/components/button/_mixins.scss index 951fbf595311..ccf7efa90f76 100644 --- a/packages/styles/scss/components/button/_mixins.scss +++ b/packages/styles/scss/components/button/_mixins.scss @@ -13,8 +13,11 @@ @use '../../type' as *; @use '../../utilities/component-reset'; @use '../../utilities/convert' as *; +@use '../../utilities/custom-property'; @use 'tokens' as *; +$button-focus-color: custom-property.get-var('button-focus-color', $focus); + @mixin button-base { @include component-reset.reset; @include type-style('body-short-01'); @@ -84,8 +87,8 @@ } &:focus { - border-color: $focus; - box-shadow: inset 0 0 0 $button-outline-width $focus, + border-color: $button-focus-color; + box-shadow: inset 0 0 0 $button-outline-width $button-focus-color, inset 0 0 0 $button-border-width $background; } diff --git a/packages/styles/scss/components/link/_link.scss b/packages/styles/scss/components/link/_link.scss index b953303ba209..508998d83be2 100644 --- a/packages/styles/scss/components/link/_link.scss +++ b/packages/styles/scss/components/link/_link.scss @@ -11,8 +11,19 @@ @use '../../theme' as *; @use '../../type'; @use '../../utilities/component-reset'; +@use '../../utilities/custom-property'; @use '../../utilities/focus-outline' as *; +$link-text-color: custom-property.get-var('link-text-color', $link-primary); +$link-hover-text-color: custom-property.get-var( + 'link-hover-text-color', + $link-primary-hover +); +$link-focus-text-color: custom-property.get-var( + 'link-focus-text-color', + $focus +); + /// Link styles /// @access public /// @group link @@ -22,13 +33,13 @@ @include type.type-style('body-short-01'); display: inline-flex; - color: $link-primary; + color: $link-text-color; outline: none; text-decoration: none; transition: color $duration-fast-01 motion(standard, productive); &:hover { - color: $link-primary-hover; + color: $link-hover-text-color; text-decoration: underline; } @@ -41,6 +52,8 @@ &:focus { @include focus-outline; + + outline-color: $link-focus-text-color; } &:visited { diff --git a/packages/styles/scss/components/toggletip/_index.scss b/packages/styles/scss/components/toggletip/_index.scss new file mode 100644 index 000000000000..89a085bf24b2 --- /dev/null +++ b/packages/styles/scss/components/toggletip/_index.scss @@ -0,0 +1,11 @@ +// +// Copyright IBM Corp. 2018, 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. +// + +@forward 'toggletip'; +@use 'toggletip'; + +@include toggletip.toggletip; diff --git a/packages/styles/scss/components/toggletip/_toggletip.scss b/packages/styles/scss/components/toggletip/_toggletip.scss new file mode 100644 index 000000000000..ee31f6c0bdba --- /dev/null +++ b/packages/styles/scss/components/toggletip/_toggletip.scss @@ -0,0 +1,81 @@ +// +// Copyright IBM Corp. 2018, 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. +// + +@use '../../config' as *; +@use '../../spacing'; +@use '../../theme'; +@use '../../type'; +@use '../../utilities/button-reset'; +@use '../../utilities/convert'; +@use '../../utilities/custom-property'; +@use '../../utilities/focus-outline'; + +@mixin toggletip() { + .#{$prefix}--toggletip-label { + @include type.type-style('label-01'); + + margin-right: spacing.$spacing-03; + + color: theme.$text-secondary; + } + + .#{$prefix}--toggletip-button { + @include button-reset.reset(); + + display: flex; + align-items: center; + } + + .#{$prefix}--toggletip-button svg { + fill: theme.$icon-secondary; + } + + .#{$prefix}--toggletip-button:hover svg, + .#{$prefix}--toggletip--open .#{$prefix}--toggletip-button svg { + fill: theme.$icon-primary; + } + + .#{$prefix}--toggletip-button:focus { + @include focus-outline.focus-outline; + } + + .#{$prefix}--toggletip { + @include custom-property.declaration('popover-offset', convert.rem(13px)); + } + + .#{$prefix}--toggletip-content { + @include custom-property.declaration( + 'button-focus-color', + theme.$focus-inverse + ); + @include custom-property.declaration( + 'link-text-color', + theme.$link-inverse + ); + @include custom-property.declaration( + 'link-hover-text-color', + theme.$link-inverse + ); + @include custom-property.declaration( + 'link-focus-text-color', + theme.$focus-inverse + ); + @include type.type-style('body-short-01'); + + display: grid; + max-width: 18rem; + padding: spacing.$spacing-05; + row-gap: spacing.$spacing-05; + } + + .#{$prefix}--toggletip-actions { + display: flex; + align-items: center; + justify-content: space-between; + column-gap: spacing.$spacing-05; + } +}