diff --git a/.changeset/ninety-mice-rush.md b/.changeset/ninety-mice-rush.md new file mode 100644 index 00000000000..3b316888eaf --- /dev/null +++ b/.changeset/ninety-mice-rush.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Add `truncation` prop to `Button` to allow for ellipses overflow or text wrapping for long button labels diff --git a/packages/react/src/Button/Button.docs.json b/packages/react/src/Button/Button.docs.json index a79de2ca1e3..53f346410ec 100644 --- a/packages/react/src/Button/Button.docs.json +++ b/packages/react/src/Button/Button.docs.json @@ -82,6 +82,12 @@ "type": "'default'\n| 'primary'\n| 'danger'\n| 'invisible'", "defaultValue": "'default'", "description": "Change the visual style of the button." + }, + { + "name": "labelWrap", + "type": "boolean", + "defaultValue": "false", + "description": "Whether the button label should wrap to multiple lines of it is longer than the button width." } ], "passthrough": { diff --git a/packages/react/src/Button/Button.features.stories.tsx b/packages/react/src/Button/Button.features.stories.tsx index db41e4f7e1e..7d328f2d336 100644 --- a/packages/react/src/Button/Button.features.stories.tsx +++ b/packages/react/src/Button/Button.features.stories.tsx @@ -1,8 +1,9 @@ import {EyeIcon, TriangleDownIcon, HeartIcon} from '@primer/octicons-react' import React, {useState} from 'react' import {Button} from '.' -import {announce} from '@primer/live-region-element' +import {Stack} from '../Stack/Stack' import Link from '../Link' +import {announce} from '@primer/live-region-element' export default { title: 'Components/Button/Features', @@ -135,3 +136,23 @@ export const Small = () => export const Medium = () => export const Large = () => + +export const LabelWrap = () => { + return ( + + + + + + + + ) +} diff --git a/packages/react/src/Button/Button.stories.tsx b/packages/react/src/Button/Button.stories.tsx index 793d5884195..7642d726e04 100644 --- a/packages/react/src/Button/Button.stories.tsx +++ b/packages/react/src/Button/Button.stories.tsx @@ -48,6 +48,11 @@ Playground.argTypes = { type: 'boolean', }, }, + labelWrap: { + control: { + type: 'boolean', + }, + }, leadingVisual: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]), trailingVisual: OcticonArgType([EyeClosedIcon, EyeIcon, SearchIcon, XIcon, HeartIcon]), trailingAction: OcticonArgType([TriangleDownIcon]), @@ -62,6 +67,7 @@ Playground.args = { trailingVisual: null, leadingVisual: null, trailingAction: null, + labelWrap: false, } export const Default = () => diff --git a/packages/react/src/Button/ButtonBase.tsx b/packages/react/src/Button/ButtonBase.tsx index 746a26dfe0b..5ca0530e967 100644 --- a/packages/react/src/Button/ButtonBase.tsx +++ b/packages/react/src/Button/ButtonBase.tsx @@ -25,6 +25,7 @@ const ButtonBase = forwardRef( alignContent = 'center', block = false, inactive, + labelWrap, ...rest } = props @@ -73,6 +74,7 @@ const ButtonBase = forwardRef( data-size={size === 'small' || size === 'large' ? size : undefined} data-no-visuals={!LeadingVisual && !TrailingVisual && !TrailingAction ? true : undefined} data-inactive={inactive ? true : undefined} + data-label-wrap={labelWrap} > {Icon ? ( diff --git a/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap b/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap index bdb67a2bd0e..03a5c0187ed 100644 --- a/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap +++ b/packages/react/src/Button/__tests__/__snapshots__/Button.test.tsx.snap @@ -163,6 +163,48 @@ exports[`Button respects block prop 1`] = ` width: 100%; } +.c0[data-label-wrap="true"] { + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + height: unset; + min-height: var(--control-medium-size,2rem); +} + +.c0[data-label-wrap="true"] [data-component="buttonContent"] { + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + padding-block: calc(var(--control-medium-paddingBlock,0.375rem) - 2px); +} + +.c0[data-label-wrap="true"] [data-component="text"] { + white-space: unset; + word-break: break-word; +} + +.c0[data-label-wrap="true"][data-size="small"] { + height: unset; + min-height: var(--control-small-size,1.75rem); +} + +.c0[data-label-wrap="true"][data-size="small"] [data-component="buttonContent"] { + padding-block: calc(var(--control-small-paddingBlock,0.25rem) - 2px); +} + +.c0[data-label-wrap="true"][data-size="large"] { + height: unset; + min-height: var(--control-large-size,2.5rem); + padding-inline: var(--control-large-paddingInline-spacious,1rem); +} + +.c0[data-label-wrap="true"][data-size="large"] [data-component="buttonContent"] { + padding-block: calc(var(--control-large-paddingBlock,0.625rem) - 2px); +} + .c0[data-inactive]:not([disabled]) { background-color: var(--button-inactive-bgColor,undefined); border-color: var(--button-inactive-bgColor,undefined); @@ -424,6 +466,48 @@ exports[`Button respects the alignContent prop 1`] = ` width: 100%; } +.c0[data-label-wrap="true"] { + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + height: unset; + min-height: var(--control-medium-size,2rem); +} + +.c0[data-label-wrap="true"] [data-component="buttonContent"] { + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + padding-block: calc(var(--control-medium-paddingBlock,0.375rem) - 2px); +} + +.c0[data-label-wrap="true"] [data-component="text"] { + white-space: unset; + word-break: break-word; +} + +.c0[data-label-wrap="true"][data-size="small"] { + height: unset; + min-height: var(--control-small-size,1.75rem); +} + +.c0[data-label-wrap="true"][data-size="small"] [data-component="buttonContent"] { + padding-block: calc(var(--control-small-paddingBlock,0.25rem) - 2px); +} + +.c0[data-label-wrap="true"][data-size="large"] { + height: unset; + min-height: var(--control-large-size,2.5rem); + padding-inline: var(--control-large-paddingInline-spacious,1rem); +} + +.c0[data-label-wrap="true"][data-size="large"] [data-component="buttonContent"] { + padding-block: calc(var(--control-large-paddingBlock,0.625rem) - 2px); +} + .c0[data-inactive]:not([disabled]) { background-color: var(--button-inactive-bgColor,undefined); border-color: var(--button-inactive-bgColor,undefined); @@ -684,6 +768,48 @@ exports[`Button respects the large size prop 1`] = ` width: 100%; } +.c0[data-label-wrap="true"] { + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + height: unset; + min-height: var(--control-medium-size,2rem); +} + +.c0[data-label-wrap="true"] [data-component="buttonContent"] { + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + padding-block: calc(var(--control-medium-paddingBlock,0.375rem) - 2px); +} + +.c0[data-label-wrap="true"] [data-component="text"] { + white-space: unset; + word-break: break-word; +} + +.c0[data-label-wrap="true"][data-size="small"] { + height: unset; + min-height: var(--control-small-size,1.75rem); +} + +.c0[data-label-wrap="true"][data-size="small"] [data-component="buttonContent"] { + padding-block: calc(var(--control-small-paddingBlock,0.25rem) - 2px); +} + +.c0[data-label-wrap="true"][data-size="large"] { + height: unset; + min-height: var(--control-large-size,2.5rem); + padding-inline: var(--control-large-paddingInline-spacious,1rem); +} + +.c0[data-label-wrap="true"][data-size="large"] [data-component="buttonContent"] { + padding-block: calc(var(--control-large-paddingBlock,0.625rem) - 2px); +} + .c0[data-inactive]:not([disabled]) { background-color: var(--button-inactive-bgColor,undefined); border-color: var(--button-inactive-bgColor,undefined); @@ -945,6 +1071,48 @@ exports[`Button respects the small size prop 1`] = ` width: 100%; } +.c0[data-label-wrap="true"] { + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + height: unset; + min-height: var(--control-medium-size,2rem); +} + +.c0[data-label-wrap="true"] [data-component="buttonContent"] { + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + padding-block: calc(var(--control-medium-paddingBlock,0.375rem) - 2px); +} + +.c0[data-label-wrap="true"] [data-component="text"] { + white-space: unset; + word-break: break-word; +} + +.c0[data-label-wrap="true"][data-size="small"] { + height: unset; + min-height: var(--control-small-size,1.75rem); +} + +.c0[data-label-wrap="true"][data-size="small"] [data-component="buttonContent"] { + padding-block: calc(var(--control-small-paddingBlock,0.25rem) - 2px); +} + +.c0[data-label-wrap="true"][data-size="large"] { + height: unset; + min-height: var(--control-large-size,2.5rem); + padding-inline: var(--control-large-paddingInline-spacious,1rem); +} + +.c0[data-label-wrap="true"][data-size="large"] [data-component="buttonContent"] { + padding-block: calc(var(--control-large-paddingBlock,0.625rem) - 2px); +} + .c0[data-inactive]:not([disabled]) { background-color: var(--button-inactive-bgColor,undefined); border-color: var(--button-inactive-bgColor,undefined); @@ -1208,6 +1376,48 @@ exports[`Button styles danger button appropriately 1`] = ` width: 100%; } +.c0[data-label-wrap="true"] { + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + height: unset; + min-height: var(--control-medium-size,2rem); +} + +.c0[data-label-wrap="true"] [data-component="buttonContent"] { + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + padding-block: calc(var(--control-medium-paddingBlock,0.375rem) - 2px); +} + +.c0[data-label-wrap="true"] [data-component="text"] { + white-space: unset; + word-break: break-word; +} + +.c0[data-label-wrap="true"][data-size="small"] { + height: unset; + min-height: var(--control-small-size,1.75rem); +} + +.c0[data-label-wrap="true"][data-size="small"] [data-component="buttonContent"] { + padding-block: calc(var(--control-small-paddingBlock,0.25rem) - 2px); +} + +.c0[data-label-wrap="true"][data-size="large"] { + height: unset; + min-height: var(--control-large-size,2.5rem); + padding-inline: var(--control-large-paddingInline-spacious,1rem); +} + +.c0[data-label-wrap="true"][data-size="large"] [data-component="buttonContent"] { + padding-block: calc(var(--control-large-paddingBlock,0.625rem) - 2px); +} + .c0[data-inactive]:not([disabled]) { background-color: var(--button-inactive-bgColor,undefined); border-color: var(--button-inactive-bgColor,undefined); @@ -1468,6 +1678,48 @@ exports[`Button styles invisible button appropriately 1`] = ` width: 100%; } +.c0[data-label-wrap="true"] { + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + height: unset; + min-height: var(--control-medium-size,2rem); +} + +.c0[data-label-wrap="true"] [data-component="buttonContent"] { + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + padding-block: calc(var(--control-medium-paddingBlock,0.375rem) - 2px); +} + +.c0[data-label-wrap="true"] [data-component="text"] { + white-space: unset; + word-break: break-word; +} + +.c0[data-label-wrap="true"][data-size="small"] { + height: unset; + min-height: var(--control-small-size,1.75rem); +} + +.c0[data-label-wrap="true"][data-size="small"] [data-component="buttonContent"] { + padding-block: calc(var(--control-small-paddingBlock,0.25rem) - 2px); +} + +.c0[data-label-wrap="true"][data-size="large"] { + height: unset; + min-height: var(--control-large-size,2.5rem); + padding-inline: var(--control-large-paddingInline-spacious,1rem); +} + +.c0[data-label-wrap="true"][data-size="large"] [data-component="buttonContent"] { + padding-block: calc(var(--control-large-paddingBlock,0.625rem) - 2px); +} + .c0[data-inactive]:not([disabled]) { background-color: var(--button-inactive-bgColor,undefined); border-color: var(--button-inactive-bgColor,undefined); @@ -1738,6 +1990,48 @@ exports[`Button styles primary button appropriately 1`] = ` width: 100%; } +.c0[data-label-wrap="true"] { + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + height: unset; + min-height: var(--control-medium-size,2rem); +} + +.c0[data-label-wrap="true"] [data-component="buttonContent"] { + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + padding-block: calc(var(--control-medium-paddingBlock,0.375rem) - 2px); +} + +.c0[data-label-wrap="true"] [data-component="text"] { + white-space: unset; + word-break: break-word; +} + +.c0[data-label-wrap="true"][data-size="small"] { + height: unset; + min-height: var(--control-small-size,1.75rem); +} + +.c0[data-label-wrap="true"][data-size="small"] [data-component="buttonContent"] { + padding-block: calc(var(--control-small-paddingBlock,0.25rem) - 2px); +} + +.c0[data-label-wrap="true"][data-size="large"] { + height: unset; + min-height: var(--control-large-size,2.5rem); + padding-inline: var(--control-large-paddingInline-spacious,1rem); +} + +.c0[data-label-wrap="true"][data-size="large"] [data-component="buttonContent"] { + padding-block: calc(var(--control-large-paddingBlock,0.625rem) - 2px); +} + .c0[data-inactive]:not([disabled]) { background-color: var(--button-inactive-bgColor,undefined); border-color: var(--button-inactive-bgColor,undefined); diff --git a/packages/react/src/Button/styles.ts b/packages/react/src/Button/styles.ts index 1ff80f6fad9..cd3e22e154e 100644 --- a/packages/react/src/Button/styles.ts +++ b/packages/react/src/Button/styles.ts @@ -304,6 +304,41 @@ export const getButtonStyles = (theme?: Theme) => { '&[data-block="block"]': { width: '100%', }, + '&[data-label-wrap="true"]': { + minWidth: 'fit-content', + height: 'unset', + minHeight: 'var(--control-medium-size, 2rem)', + + '[data-component="buttonContent"]': { + flex: '1 1 auto', + alignSelf: 'stretch', + paddingBlock: 'calc(var(--control-medium-paddingBlock, 0.375rem) - 2px)', + }, + + '[data-component="text"]': { + whiteSpace: 'unset', + wordBreak: 'break-word', + }, + + '&[data-size="small"]': { + height: 'unset', + minHeight: 'var(--control-small-size, 1.75rem)', + + '[data-component="buttonContent"]': { + paddingBlock: 'calc(var(--control-small-paddingBlock, 0.25rem) - 2px)', + }, + }, + + '&[data-size="large"]': { + height: 'unset', + minHeight: 'var(--control-large-size, 2.5rem)', + paddingInline: 'var(--control-large-paddingInline-spacious, 1rem)', + + '[data-component="buttonContent"]': { + paddingBlock: 'calc(var(--control-large-paddingBlock, 0.625rem) - 2px)', + }, + }, + }, '&[data-inactive]:not([disabled])': { backgroundColor: `var(--button-inactive-bgColor, ${theme?.colors.btn.inactive.bg})`, borderColor: `var(--button-inactive-bgColor, ${theme?.colors.btn.inactive.bg})`, diff --git a/packages/react/src/Button/types.ts b/packages/react/src/Button/types.ts index c41e3a0e5c5..f1552d8b474 100644 --- a/packages/react/src/Button/types.ts +++ b/packages/react/src/Button/types.ts @@ -42,6 +42,10 @@ export type ButtonBaseProps = { * interactions as an enabled button. */ inactive?: boolean + /** + * Whether the button label should wrap to multiple lines of it is longer than the button width. + */ + labelWrap?: boolean } & SxProp & React.ButtonHTMLAttributes diff --git a/packages/react/src/__tests__/__snapshots__/TextInput.test.tsx.snap b/packages/react/src/__tests__/__snapshots__/TextInput.test.tsx.snap index 998aa441866..f3e4be334ce 100644 --- a/packages/react/src/__tests__/__snapshots__/TextInput.test.tsx.snap +++ b/packages/react/src/__tests__/__snapshots__/TextInput.test.tsx.snap @@ -1824,6 +1824,48 @@ exports[`TextInput renders trailingAction icon button 1`] = ` width: 100%; } +.c4[data-label-wrap="true"] { + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + height: unset; + min-height: var(--control-medium-size,2rem); +} + +.c4[data-label-wrap="true"] [data-component="buttonContent"] { + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + padding-block: calc(var(--control-medium-paddingBlock,0.375rem) - 2px); +} + +.c4[data-label-wrap="true"] [data-component="text"] { + white-space: unset; + word-break: break-word; +} + +.c4[data-label-wrap="true"][data-size="small"] { + height: unset; + min-height: var(--control-small-size,1.75rem); +} + +.c4[data-label-wrap="true"][data-size="small"] [data-component="buttonContent"] { + padding-block: calc(var(--control-small-paddingBlock,0.25rem) - 2px); +} + +.c4[data-label-wrap="true"][data-size="large"] { + height: unset; + min-height: var(--control-large-size,2.5rem); + padding-inline: var(--control-large-paddingInline-spacious,1rem); +} + +.c4[data-label-wrap="true"][data-size="large"] [data-component="buttonContent"] { + padding-block: calc(var(--control-large-paddingBlock,0.625rem) - 2px); +} + .c4[data-inactive]:not([disabled]) { background-color: var(--button-inactive-bgColor,var(--button-inactive-bgColor-rest,var(--color-btn-inactive-bg,#eaeef2))); border-color: var(--button-inactive-bgColor,var(--button-inactive-bgColor-rest,var(--color-btn-inactive-bg,#eaeef2))); @@ -2393,6 +2435,48 @@ exports[`TextInput renders trailingAction text button 1`] = ` width: 100%; } +.c4[data-label-wrap="true"] { + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + height: unset; + min-height: var(--control-medium-size,2rem); +} + +.c4[data-label-wrap="true"] [data-component="buttonContent"] { + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + padding-block: calc(var(--control-medium-paddingBlock,0.375rem) - 2px); +} + +.c4[data-label-wrap="true"] [data-component="text"] { + white-space: unset; + word-break: break-word; +} + +.c4[data-label-wrap="true"][data-size="small"] { + height: unset; + min-height: var(--control-small-size,1.75rem); +} + +.c4[data-label-wrap="true"][data-size="small"] [data-component="buttonContent"] { + padding-block: calc(var(--control-small-paddingBlock,0.25rem) - 2px); +} + +.c4[data-label-wrap="true"][data-size="large"] { + height: unset; + min-height: var(--control-large-size,2.5rem); + padding-inline: var(--control-large-paddingInline-spacious,1rem); +} + +.c4[data-label-wrap="true"][data-size="large"] [data-component="buttonContent"] { + padding-block: calc(var(--control-large-paddingBlock,0.625rem) - 2px); +} + .c4[data-inactive]:not([disabled]) { background-color: var(--button-inactive-bgColor,var(--button-inactive-bgColor-rest,var(--color-btn-inactive-bg,#eaeef2))); border-color: var(--button-inactive-bgColor,var(--button-inactive-bgColor-rest,var(--color-btn-inactive-bg,#eaeef2))); @@ -2820,6 +2904,48 @@ exports[`TextInput renders trailingAction text button with a tooltip 1`] = ` width: 100%; } +.c4[data-label-wrap="true"] { + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + height: unset; + min-height: var(--control-medium-size,2rem); +} + +.c4[data-label-wrap="true"] [data-component="buttonContent"] { + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + padding-block: calc(var(--control-medium-paddingBlock,0.375rem) - 2px); +} + +.c4[data-label-wrap="true"] [data-component="text"] { + white-space: unset; + word-break: break-word; +} + +.c4[data-label-wrap="true"][data-size="small"] { + height: unset; + min-height: var(--control-small-size,1.75rem); +} + +.c4[data-label-wrap="true"][data-size="small"] [data-component="buttonContent"] { + padding-block: calc(var(--control-small-paddingBlock,0.25rem) - 2px); +} + +.c4[data-label-wrap="true"][data-size="large"] { + height: unset; + min-height: var(--control-large-size,2.5rem); + padding-inline: var(--control-large-paddingInline-spacious,1rem); +} + +.c4[data-label-wrap="true"][data-size="large"] [data-component="buttonContent"] { + padding-block: calc(var(--control-large-paddingBlock,0.625rem) - 2px); +} + .c4[data-inactive]:not([disabled]) { background-color: var(--button-inactive-bgColor,var(--button-inactive-bgColor-rest,var(--color-btn-inactive-bg,#eaeef2))); border-color: var(--button-inactive-bgColor,var(--button-inactive-bgColor-rest,var(--color-btn-inactive-bg,#eaeef2)));