diff --git a/.changeset/happy-panthers-buy.md b/.changeset/happy-panthers-buy.md new file mode 100644 index 0000000000..09d898e2f3 --- /dev/null +++ b/.changeset/happy-panthers-buy.md @@ -0,0 +1,6 @@ +--- +"@twilio-paste/tooltip": minor +"@twilio-paste/core": minor +--- + +[Tooltip] added the ability to put keyboard combinations using the KeyboardKey in the tooltip diff --git a/packages/paste-core/components/code-block/package.json b/packages/paste-core/components/code-block/package.json index 730d516b39..38408d358c 100644 --- a/packages/paste-core/components/code-block/package.json +++ b/packages/paste-core/components/code-block/package.json @@ -36,6 +36,7 @@ "@twilio-paste/flex": "^8.0.0", "@twilio-paste/heading": "^11.0.0", "@twilio-paste/icons": "^12.0.0", + "@twilio-paste/keyboard-key": "^0.0.0", "@twilio-paste/reakit-library": "^2.0.0", "@twilio-paste/screen-reader-only": "^13.0.0", "@twilio-paste/spinner": "^14.0.0", @@ -68,6 +69,7 @@ "@twilio-paste/flex": "^8.1.0", "@twilio-paste/heading": "^11.1.0", "@twilio-paste/icons": "^12.2.0", + "@twilio-paste/keyboard-key": "^0.0.0", "@twilio-paste/reakit-library": "^2.1.1", "@twilio-paste/screen-reader-only": "^13.1.0", "@twilio-paste/spinner": "^14.1.2", diff --git a/packages/paste-core/components/tooltip/package.json b/packages/paste-core/components/tooltip/package.json index 790722e29a..02d2b8bacc 100644 --- a/packages/paste-core/components/tooltip/package.json +++ b/packages/paste-core/components/tooltip/package.json @@ -31,8 +31,10 @@ "@twilio-paste/customization": "^8.0.0", "@twilio-paste/design-tokens": "^10.0.0", "@twilio-paste/icons": "^12.0.0", + "@twilio-paste/keyboard-key": "^0.0.0", "@twilio-paste/reakit-library": "^2.0.0", "@twilio-paste/spinner": "^14.0.0", + "@twilio-paste/stack": "^8.1.0", "@twilio-paste/style-props": "^9.0.0", "@twilio-paste/styling-library": "^3.0.0", "@twilio-paste/text": "^10.0.0", @@ -52,8 +54,10 @@ "@twilio-paste/customization": "^8.1.0", "@twilio-paste/design-tokens": "^10.2.0", "@twilio-paste/icons": "^12.2.0", + "@twilio-paste/keyboard-key": "^0.0.0", "@twilio-paste/reakit-library": "^2.1.0", "@twilio-paste/spinner": "^14.1.0", + "@twilio-paste/stack": "^8.1.0", "@twilio-paste/style-props": "^9.1.0", "@twilio-paste/styling-library": "^3.0.0", "@twilio-paste/text": "^10.1.0", diff --git a/packages/paste-core/components/tooltip/src/Tooltip.tsx b/packages/paste-core/components/tooltip/src/Tooltip.tsx index 6ce4fa7735..06f6a55d2d 100644 --- a/packages/paste-core/components/tooltip/src/Tooltip.tsx +++ b/packages/paste-core/components/tooltip/src/Tooltip.tsx @@ -1,5 +1,7 @@ import { Box, safelySpreadBoxProps } from "@twilio-paste/box"; import type { BoxProps } from "@twilio-paste/box"; +import { KeyboardKey, KeyboardKeyGroup } from "@twilio-paste/keyboard-key"; +import { Stack } from "@twilio-paste/stack"; import { Text } from "@twilio-paste/text"; import { StyledBase } from "@twilio-paste/theme"; import { TooltipPrimitive, TooltipPrimitiveReference, useTooltipPrimitiveState } from "@twilio-paste/tooltip-primitive"; @@ -60,17 +62,48 @@ export interface TooltipProps extends TooltipPrimitiveInitialState { * @memberof TooltipProps */ text: string; + actionHeader?: never; + keyCombinationsActions?: never; } +interface KeyboardActions { + name: string; + eventKeyCombination: string[]; + disabled?: boolean; +} + +export interface KeyboardKeyTooltipProps + extends Omit { + text?: never; + /** + * The mapping of action names to their respective key combinations. + * + * @type {Array} + * @memberof KeyboardKeyTooltipProps + */ + keyCombinationsActions: Array; + /** + * The header content of the Tooltip. + * + * @type {string} + * @memberof KeyboardKeyTooltipProps + */ + actionHeader?: string; +} + +// Union will stop users from adding types from both TooltipProps and KeyboardKeyTooltipProps at the same time. +export type TooltipVariantProps = TooltipProps | KeyboardKeyTooltipProps; + /* *Tooltip's current structure does not allow for customization of its arrow. *TODO: refactor Tooltip so that the styling of its arrow can be customized *using Customization Provider. */ -const Tooltip = React.forwardRef( - ({ baseId, children, element = "TOOLTIP", state, text, ...props }, ref) => { +const Tooltip = React.forwardRef( + ({ baseId, children, element = "TOOLTIP", state, text, actionHeader, keyCombinationsActions, ...props }, ref) => { const tooltip = state || useTooltipPrimitiveState({ baseId: `paste-tooltip-${useUID()}`, ...props }); + return ( <> {React.Children.only( @@ -82,15 +115,62 @@ const Tooltip = React.forwardRef( {/* import Paste Theme Based Styles due to portal positioning. */} - - {text} - + {text && !keyCombinationsActions && ( + + {text} + + )} + {keyCombinationsActions && !text && ( + + + {actionHeader && ( + + {actionHeader} + + )} + {keyCombinationsActions.map((action, idx) => ( + + {action.name && ( + + {action.name} + + )} + + {action.eventKeyCombination.map((key, i) => ( + + {key} + + ))} + + + ))} + + + )} diff --git a/packages/paste-core/components/tooltip/src/index.tsx b/packages/paste-core/components/tooltip/src/index.tsx index ffeacc73c3..8165961c29 100644 --- a/packages/paste-core/components/tooltip/src/index.tsx +++ b/packages/paste-core/components/tooltip/src/index.tsx @@ -3,6 +3,8 @@ import type { TooltipStateReturn } from "./Tooltip"; export { Tooltip, useTooltipState } from "./Tooltip"; export type { TooltipProps, + TooltipVariantProps, + KeyboardKeyTooltipProps, TooltipStateReturn, UseTooltipInitialStateProps, } from "./Tooltip"; diff --git a/packages/paste-core/components/tooltip/stories/keyboard-key.stories.tsx b/packages/paste-core/components/tooltip/stories/keyboard-key.stories.tsx new file mode 100644 index 0000000000..62fcc6b822 --- /dev/null +++ b/packages/paste-core/components/tooltip/stories/keyboard-key.stories.tsx @@ -0,0 +1,137 @@ +import type { StoryFn } from "@storybook/react"; +import { Anchor } from "@twilio-paste/anchor"; +import { Box } from "@twilio-paste/box"; +import { Button } from "@twilio-paste/button"; +import { CustomizationProvider } from "@twilio-paste/customization"; +import { InformationIcon } from "@twilio-paste/icons/esm/InformationIcon"; +import { Stack } from "@twilio-paste/stack"; +import { Text } from "@twilio-paste/text"; +import { Theme, useTheme } from "@twilio-paste/theme"; +import * as React from "react"; + +import { Tooltip, useTooltipState } from "../src"; + +// eslint-disable-next-line import/no-default-export +export default { + title: "Components/Tooltip/KeyboardKey", + excludeStories: ["StateHookExample"], + component: Tooltip, + parameters: { + chromatic: { delay: 3000, diffThreshold: 0.2 }, + }, +}; + +export const Default = (): React.ReactNode => { + return ( + + + + + + ); +}; + +export const CustomizedTooltip: StoryFn = (_args, { parameters: { isTestEnvironment } }) => { + const currentTheme = useTheme(); + return ( + + + + + + + + + + + + + + + ); +}; + +CustomizedTooltip.storyName = "Customized Tooltip"; +CustomizedTooltip.parameters = { + parameters: { + chromatic: { disableSnapshot: true }, + }, + a11y: { + // no need to a11y check customization + disable: true, + }, +}; diff --git a/packages/paste-core/components/tooltip/type-docs.json b/packages/paste-core/components/tooltip/type-docs.json index 98bc9cae97..91430482ed 100644 --- a/packages/paste-core/components/tooltip/type-docs.json +++ b/packages/paste-core/components/tooltip/type-docs.json @@ -7,6 +7,110 @@ "externalProp": false, "description": "The text content of the Tooltip." }, + "actionHeader": { + "type": "never", + "defaultValue": null, + "required": false, + "externalProp": false + }, + "animated": { + "type": "number | boolean", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "If `true`, `animating` will be set to `true` when `visible` is updated.\nIt'll wait for `stopAnimation` to be called or a CSS transition ends.\nIf `animated` is set to a `number`, `stopAnimation` will be called only\nafter the same number of milliseconds have passed." + }, + "baseId": { + "type": "string", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "ID that will serve as a base for all the items IDs." + }, + "element": { + "type": "string", + "defaultValue": "TOOLTIP", + "required": false, + "externalProp": false, + "description": "Overrides the default element name to apply unique styles with the Customization Provider." + }, + "gutter": { + "type": "number", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Offset between the reference and the popover on the main axis. Should not be combined with `unstable_offset`." + }, + "keyCombinationsActions": { + "type": "never", + "defaultValue": null, + "required": false, + "externalProp": false + }, + "placement": { + "type": "Placement", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Actual `placement`." + }, + "state": { + "type": "TooltipStateReturn", + "defaultValue": null, + "required": false, + "externalProp": false, + "description": "The returned state from the `useTooltipState` hook." + }, + "unstable_fixed": { + "type": "boolean", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Whether or not the popover should have `position` set to `fixed`." + }, + "unstable_flip": { + "type": "boolean", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Flip the popover's placement when it starts to overlap its reference\nelement." + }, + "unstable_offset": { + "type": "[string | number, string | number]", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Offset between the reference and the popover: [main axis, alt axis]. Should not be combined with `gutter`." + }, + "unstable_preventOverflow": { + "type": "boolean", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Prevents popover from being positioned outside the boundary." + }, + "unstable_timeout": { + "type": "number", + "defaultValue": null, + "required": false, + "externalProp": true + }, + "visible": { + "type": "boolean", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Whether it's visible or not." + } + }, + "TooltipVariant": { + "actionHeader": { + "type": "string", + "defaultValue": null, + "required": false, + "externalProp": false, + "description": "The header content of the Tooltip." + }, "animated": { "type": "number | boolean", "defaultValue": null, @@ -35,6 +139,13 @@ "externalProp": true, "description": "Offset between the reference and the popover on the main axis. Should not be combined with `unstable_offset`." }, + "keyCombinationsActions": { + "type": "KeyboardActions[]", + "defaultValue": null, + "required": false, + "externalProp": false, + "description": "The mapping of action names to their respective key combinations." + }, "placement": { "type": "Placement", "defaultValue": null, @@ -49,6 +160,118 @@ "externalProp": false, "description": "The returned state from the `useTooltipState` hook." }, + "text": { + "type": "string", + "defaultValue": null, + "required": false, + "externalProp": false, + "description": "The text content of the Tooltip." + }, + "unstable_fixed": { + "type": "boolean", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Whether or not the popover should have `position` set to `fixed`." + }, + "unstable_flip": { + "type": "boolean", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Flip the popover's placement when it starts to overlap its reference\nelement." + }, + "unstable_offset": { + "type": "[string | number, string | number]", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Offset between the reference and the popover: [main axis, alt axis]. Should not be combined with `gutter`." + }, + "unstable_preventOverflow": { + "type": "boolean", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Prevents popover from being positioned outside the boundary." + }, + "unstable_timeout": { + "type": "number", + "defaultValue": null, + "required": false, + "externalProp": true + }, + "visible": { + "type": "boolean", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Whether it's visible or not." + } + }, + "KeyboardKeyTooltip": { + "keyCombinationsActions": { + "type": "KeyboardActions[]", + "defaultValue": null, + "required": true, + "externalProp": false, + "description": "The mapping of action names to their respective key combinations." + }, + "actionHeader": { + "type": "string", + "defaultValue": null, + "required": false, + "externalProp": false, + "description": "The header content of the Tooltip." + }, + "animated": { + "type": "number | boolean", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "If `true`, `animating` will be set to `true` when `visible` is updated.\nIt'll wait for `stopAnimation` to be called or a CSS transition ends.\nIf `animated` is set to a `number`, `stopAnimation` will be called only\nafter the same number of milliseconds have passed." + }, + "baseId": { + "type": "string", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "ID that will serve as a base for all the items IDs." + }, + "element": { + "type": "string", + "defaultValue": "TOOLTIP", + "required": false, + "externalProp": false, + "description": "Overrides the default element name to apply unique styles with the Customization Provider." + }, + "gutter": { + "type": "number", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Offset between the reference and the popover on the main axis. Should not be combined with `unstable_offset`." + }, + "placement": { + "type": "Placement", + "defaultValue": null, + "required": false, + "externalProp": true, + "description": "Actual `placement`." + }, + "state": { + "type": "TooltipStateReturn", + "defaultValue": null, + "required": false, + "externalProp": false, + "description": "The returned state from the `useTooltipState` hook." + }, + "text": { + "type": "never", + "defaultValue": null, + "required": false, + "externalProp": false + }, "unstable_fixed": { "type": "boolean", "defaultValue": null, diff --git a/yarn.lock b/yarn.lock index f2461990ca..fb8b9ca970 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12067,6 +12067,7 @@ __metadata: "@twilio-paste/flex": ^8.1.0 "@twilio-paste/heading": ^11.1.0 "@twilio-paste/icons": ^12.2.0 + "@twilio-paste/keyboard-key": ^0.0.0 "@twilio-paste/reakit-library": ^2.1.1 "@twilio-paste/screen-reader-only": ^13.1.0 "@twilio-paste/spinner": ^14.1.2 @@ -12100,6 +12101,7 @@ __metadata: "@twilio-paste/flex": ^8.0.0 "@twilio-paste/heading": ^11.0.0 "@twilio-paste/icons": ^12.0.0 + "@twilio-paste/keyboard-key": ^0.0.0 "@twilio-paste/reakit-library": ^2.0.0 "@twilio-paste/screen-reader-only": ^13.0.0 "@twilio-paste/spinner": ^14.0.0 @@ -15716,8 +15718,10 @@ __metadata: "@twilio-paste/customization": ^8.1.0 "@twilio-paste/design-tokens": ^10.2.0 "@twilio-paste/icons": ^12.2.0 + "@twilio-paste/keyboard-key": ^0.0.0 "@twilio-paste/reakit-library": ^2.1.0 "@twilio-paste/spinner": ^14.1.0 + "@twilio-paste/stack": ^8.1.0 "@twilio-paste/style-props": ^9.1.0 "@twilio-paste/styling-library": ^3.0.0 "@twilio-paste/text": ^10.1.0 @@ -15738,8 +15742,10 @@ __metadata: "@twilio-paste/customization": ^8.0.0 "@twilio-paste/design-tokens": ^10.0.0 "@twilio-paste/icons": ^12.0.0 + "@twilio-paste/keyboard-key": ^0.0.0 "@twilio-paste/reakit-library": ^2.0.0 "@twilio-paste/spinner": ^14.0.0 + "@twilio-paste/stack": ^8.1.0 "@twilio-paste/style-props": ^9.0.0 "@twilio-paste/styling-library": ^3.0.0 "@twilio-paste/text": ^10.0.0