Skip to content
50 changes: 50 additions & 0 deletions packages/react/src/Token/_RemoveTokenButton.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
.TokenButton {
display: inline-flex;
padding: 0;
margin-left: var(--base-size-4);
font-family: inherit;
color: currentColor;
text-decoration: none;
cursor: pointer;
user-select: none;
background-color: transparent;
border: 0;
border-radius: var(--borderRadius-full);
justify-content: center;
align-items: center;
appearance: none;
align-self: baseline;
}

.TokenButton[data-size='small'] {
width: var(--base-size-16);
height: var(--base-size-16);
}

.TokenButton[data-size='medium'] {
width: 20px;
height: 20px;
}

.TokenButton[data-size='large'] {
width: var(--base-size-24);
height: var(--base-size-24);
margin-left: var(--base-size-8);
}

.TokenButton[data-size='xlarge'] {
width: var(--base-size-32);
height: var(--base-size-32);
margin-left: var(--base-size-8);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious could you add this to the .TokenButton[data-size='xlarge'] and .TokenButton[data-size='large'] selectors instead of adding .Bigger?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good callout! Update

}

.TokenButton:hover,
.TokenButton:focus {
/* TODO: choose a better functional color variable for this */
background-color: var(--bgColor-neutral-muted);
}

.TokenButton:active {
/* TODO: choose a better functional color variable for this */
background-color: var(--bgColor-neutral-muted);
}
115 changes: 69 additions & 46 deletions packages/react/src/Token/_RemoveTokenButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@ import React from 'react'
import {XIcon} from '@primer/octicons-react'
import styled, {css} from 'styled-components'
import {variant} from 'styled-system'
import {clsx} from 'clsx'
import {get} from '../constants'
import type {SxProp} from '../sx'
import sx from '../sx'
import type {ComponentProps} from '../utils/types'
import sx, {type SxProp} from '../sx'
import type {TokenSizeKeys} from './TokenBase'
import {tokenSizes, defaultTokenSize} from './TokenBase'
import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent'
import {useFeatureFlag} from '../FeatureFlags'

interface TokenButtonProps {
import classes from './_RemoveTokenButton.module.css'

interface TokenButtonProps extends SxProp {
borderOffset?: number
size?: TokenSizeKeys
isParentInteractive?: boolean
}

const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team'

const variants = variant<{height: string; width: string}, TokenSizeKeys>({
prop: 'size',
variants: {
Expand All @@ -39,66 +44,84 @@ const variants = variant<{height: string; width: string}, TokenSizeKeys>({

const getTokenButtonIconSize = (size?: TokenSizeKeys) => parseInt(tokenSizes[size || defaultTokenSize], 10) * 0.75

const StyledTokenButton = styled.span<TokenButtonProps & SxProp>`
background-color: transparent;
font-family: inherit;
color: currentColor;
cursor: pointer;
display: inline-flex;
justify-content: center;
align-items: center;
user-select: none;
appearance: none;
text-decoration: none;
padding: 0;
transform: ${props => `translate(${props.borderOffset}px, -${props.borderOffset}px)`};
align-self: baseline;
border: 0;
border-radius: 999px;
const StyledTokenButton = toggleStyledComponent(
CSS_MODULES_FEATURE_FLAG,
'span',
styled.span<TokenButtonProps>`
background-color: transparent;
font-family: inherit;
color: currentColor;
cursor: pointer;
display: inline-flex;
justify-content: center;
align-items: center;
user-select: none;
appearance: none;
text-decoration: none;
padding: 0;
transform: ${props => `translate(${props.borderOffset}px, -${props.borderOffset}px)`};
align-self: baseline;
border: 0;
border-radius: 999px;

${props => {
switch (props.size) {
case 'large':
case 'xlarge':
return css`
margin-left: ${get('space.2')};
`
default:
return css`
margin-left: ${get('space.1')};
`
}
}}

${props => {
switch (props.size) {
case 'large':
case 'xlarge':
return css`
margin-left: ${get('space.2')};
`
default:
return css`
margin-left: ${get('space.1')};
`
&:hover,
&:focus {
// TODO: choose a better functional color variable for this
background-color: ${get('colors.neutral.muted')};
}
}}

&:hover,
&:focus {
// TODO: choose a better functional color variable for this
background-color: ${get('colors.neutral.muted')};
}
&:active {
// TODO: choose a better functional color variable for this
background-color: ${get('colors.neutral.subtle')};
}

&:active {
// TODO: choose a better functional color variable for this
background-color: ${get('colors.neutral.subtle')};
}
${variants}
${sx}
`,
)

${variants}
${sx}
`
type RemoveTokenButtonProps = TokenButtonProps & Omit<React.HTMLProps<HTMLSpanElement | HTMLButtonElement>, 'size'>

const RemoveTokenButton: React.FC<React.PropsWithChildren<ComponentProps<typeof StyledTokenButton>>> = ({
const RemoveTokenButton = ({
'aria-label': ariaLabel,
isParentInteractive,
size = defaultTokenSize,
className,
...rest
}) => {
}: React.PropsWithChildren<RemoveTokenButtonProps>) => {
delete rest.children

const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)

return (
<StyledTokenButton
as={isParentInteractive ? 'span' : 'button'}
tabIndex={isParentInteractive ? -1 : undefined}
aria-label={!isParentInteractive ? 'Remove token' : ariaLabel}
size={size}
data-size={size}
className={clsx(enabled && classes.TokenButton, className)}
style={
enabled
? {
transform: `translate(${rest.borderOffset}px, -${rest.borderOffset}px)`,
}
: {}
}
{...rest}
>
<XIcon size={getTokenButtonIconSize(size)} />
Expand Down
46 changes: 46 additions & 0 deletions packages/react/src/Token/_TokenTextContainer.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.TokenTextContainer {
width: auto;
min-width: 0;
padding: 0;
margin: 0;
overflow: hidden;
font: inherit;
/* stylelint-disable-next-line primer/typography */
line-height: normal;
color: inherit;

/* reset anchor styles */
color: currentColor;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;

/* reset button styles, make the cursor a pointer, and add line-height */
background: transparent;
border: none;
flex-grow: 1;
-webkit-font-smoothing: inherit;
-moz-osx-font-smoothing: inherit;
appearance: none;
}

/* Position psuedo-element above text content, but below the
remove button.
This ensures the <a> or <button> receives the click no
matter where on the token the user clicks. */
.TokenTextContainer:is(a, button, [tabIndex='0']) {
cursor: pointer;
}

/* Position psuedo-element above text content, but below the
remove button.
This ensures the <a> or <button> receives the click no
matter where on the token the user clicks. */
.TokenTextContainer:is(a, button, [tabIndex='0'])::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: '';
}
101 changes: 61 additions & 40 deletions packages/react/src/Token/_TokenTextContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,67 @@
import styled from 'styled-components'
import type {TokenBaseProps} from './TokenBase'
import {toggleStyledComponent} from '../internal/utils/toggleStyledComponent'
import React from 'react'
import classes from './_TokenTextContainer.module.css'
import {useFeatureFlag} from '../FeatureFlags'
import {clsx} from 'clsx'

const TokenTextContainer = styled('span')<Partial<TokenBaseProps>>`
flex-grow: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;

// reset button styles, make the cursor a pointer, and add line-height
background: transparent;
border: none;
color: inherit;
font: inherit;
margin: 0;
padding: 0;
width: auto;
-webkit-font-smoothing: inherit;
-moz-osx-font-smoothing: inherit;
-webkit-appearance: none;
line-height: normal;

// reset anchor styles
color: currentColor;
text-decoration: none;

// Position psuedo-element above text content, but below the
// remove button.
// This ensures the <a> or <button> receives the click no
// matter where on the token the user clicks.
&:is(a, button, [tabIndex='0']) {
cursor: pointer;

&:after {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
const CSS_MODULES_FEATURE_FLAG = 'primer_react_css_modules_team'

const StyledTokenTextContainer = toggleStyledComponent(
CSS_MODULES_FEATURE_FLAG,
'span',
styled('span')`
flex-grow: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;

// reset button styles, make the cursor a pointer, and add line-height
background: transparent;
border: none;
color: inherit;
font: inherit;
margin: 0;
padding: 0;
width: auto;
-webkit-font-smoothing: inherit;
-moz-osx-font-smoothing: inherit;
-webkit-appearance: none;
line-height: normal;

// reset anchor styles
color: currentColor;
text-decoration: none;

// Position psuedo-element above text content, but below the
// remove button.
// This ensures the <a> or <button> receives the click no
// matter where on the token the user clicks.
&:is(a, button, [tabIndex='0']) {
cursor: pointer;

&:after {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
}
}
`
`,
)

const TokenTextContainer = ({children, ...props}: React.PropsWithChildren<Partial<TokenBaseProps>>) => {
const enabled = useFeatureFlag(CSS_MODULES_FEATURE_FLAG)

return (
<StyledTokenTextContainer className={clsx(enabled && classes.TokenTextContainer)} {...props}>
{children}
</StyledTokenTextContainer>
)
}

export default TokenTextContainer
Loading
Loading