11import styled from 'styled-components' ;
22import { spacing } from '../../spacing' ;
33import { fontWeight } from '../../style/theme' ;
4+ import { Icon } from '../icon/Icon.component' ;
45
56type TextBadgeVariant =
67 | 'statusHealthy'
@@ -10,37 +11,101 @@ type TextBadgeVariant =
1011 | 'infoSecondary'
1112 | 'selectedActive' ;
1213
13- const StyledTextBadge = styled . span < { variant : TextBadgeVariant } > `
14- ${ ( { theme, variant } ) => `
15- background-color: ${ theme [ variant ] } ;
14+ // Lets callers drive the badge colors directly instead of picking one of the
15+ // fixed `variant`s — e.g. a per-item color computed at runtime. When set, it
16+ // also gives the badge a border (the `variant` badges have none).
17+ export type TextBadgeCustomColor = {
18+ /** Text color, and border color unless `borderColor` is given. */
19+ text : string ;
20+ /** Background color. */
21+ backgroundColor : string ;
22+ /** Border color. Falls back to `text`. */
23+ borderColor ?: string ;
24+ } ;
25+
26+ const StyledTextBadge = styled . span < {
27+ variant : TextBadgeVariant ;
28+ $customColor ?: TextBadgeCustomColor ;
29+ $removable : boolean ;
30+ } > `
31+ ${ ( { theme, variant, $customColor, $removable } ) => `
32+ ${ $removable ? `display: inline-flex; align-items: center; gap: ${ spacing . r4 } ;` : '' }
33+ background-color: ${ $customColor ? $customColor . backgroundColor : theme [ variant ] } ;
1634 color: ${
17- variant === 'infoSecondary' ? theme . textPrimary : theme . textReverse
35+ $customColor
36+ ? $customColor . text
37+ : variant === 'infoSecondary'
38+ ? theme . textPrimary
39+ : theme . textReverse
1840 } ;
41+ ${ $customColor ? `border: 1px solid ${ $customColor . borderColor ?? $customColor . text } ;` : '' }
1942 padding: 2px ${ spacing . r4 } ;
2043 border-radius: 4px;
2144 font-size: 0.9rem;
2245 font-weight: ${ fontWeight . bold } ;
2346 margin: 0 ${ spacing . r4 } 0 ${ spacing . r4 } ;
2447 ` }
2548` ;
49+
50+ const RemoveButton = styled . button `
51+ display: inline-flex;
52+ align-items: center;
53+ justify-content: center;
54+ padding: 0;
55+ border: none;
56+ background: none;
57+ color: inherit;
58+ cursor: pointer;
59+ opacity: 0.8;
60+
61+ &:hover {
62+ opacity: 1;
63+ }
64+ ` ;
65+
2666type Props = {
2767 text : React . ReactNode ;
2868 className ?: string ;
2969 variant ?: TextBadgeVariant ;
70+ /** Override the variant colors with explicit ones (and add a border). */
71+ customColor ?: TextBadgeCustomColor ;
72+ /**
73+ * When provided, a trailing "✕" button is rendered and this is called when
74+ * the user clicks it. The badge does not track any in-flight state itself —
75+ * guard against repeated calls in the handler if needed.
76+ */
77+ onRemove ?: ( ) => void ;
78+ /** Accessible label for the remove button. Defaults to "Remove". */
79+ removeAriaLabel ?: string ;
3080} & React . HTMLAttributes < HTMLSpanElement > ;
3181export function TextBadge ( {
3282 text,
3383 variant = 'infoPrimary' ,
3484 className,
85+ customColor,
86+ onRemove,
87+ removeAriaLabel = 'Remove' ,
3588 ...rest
3689} : Props ) {
3790 return (
3891 < StyledTextBadge
3992 className = { [ 'sc-text-badge' , className ] . join ( ' ' ) }
4093 variant = { variant }
94+ $customColor = { customColor }
95+ $removable = { Boolean ( onRemove ) }
4196 { ...rest }
4297 >
43- { text }
98+ { onRemove ? < span > { text } </ span > : text }
99+ { onRemove && (
100+ < RemoveButton
101+ type = "button"
102+ aria-label = { removeAriaLabel }
103+ onClick = { onRemove }
104+ className = "sc-text-badge-remove"
105+ >
106+ < Icon name = "Close" size = "xs" />
107+ </ RemoveButton >
108+ ) }
44109 </ StyledTextBadge >
45110 ) ;
46111}
0 commit comments