1
1
import * as React from 'react' ;
2
2
3
3
import type { LocalizationKey } from '../customizables' ;
4
- import { Box , Dd , descriptors , Dl , Dt , Span } from '../customizables' ;
4
+ import { Box , Button , Dd , descriptors , Dl , Dt , Icon , Span } from '../customizables' ;
5
+ import { useClipboard } from '../hooks' ;
6
+ import { Check , Copy } from '../icons' ;
5
7
import { common } from '../styledSystem' ;
8
+ import { truncateWithEndVisible } from '../utils/truncateTextWithEndVisible' ;
6
9
7
10
/* -------------------------------------------------------------------------------------------------
8
11
* LineItems.Root
@@ -95,7 +98,6 @@ function Title({ title, description }: TitleProps) {
95
98
sx = { t => ( {
96
99
display : 'grid' ,
97
100
color : variant === 'primary' ? t . colors . $colorText : t . colors . $colorTextSecondary ,
98
- marginTop : variant !== 'primary' ? t . space . $0x25 : undefined ,
99
101
...common . textVariants ( t ) [ textVariant ] ,
100
102
} ) }
101
103
>
@@ -120,11 +122,26 @@ function Title({ title, description }: TitleProps) {
120
122
121
123
interface DescriptionProps {
122
124
text : string | LocalizationKey ;
125
+ /**
126
+ * When true, the text will be truncated with an ellipsis in the middle and the last 5 characters will be visible.
127
+ * @default `false`
128
+ */
129
+ truncateText ?: boolean ;
130
+ /**
131
+ * When true, there will be a button to copy the providedtext.
132
+ * @default `false`
133
+ */
134
+ copyText ?: boolean ;
135
+ /**
136
+ * The visually hidden label for the copy button.
137
+ * @default `Copy`
138
+ */
139
+ copyLabel ?: string ;
123
140
prefix ?: string | LocalizationKey ;
124
141
suffix ?: string | LocalizationKey ;
125
142
}
126
143
127
- function Description ( { text, prefix, suffix } : DescriptionProps ) {
144
+ function Description ( { text, prefix, suffix, truncateText = false , copyText = false , copyLabel } : DescriptionProps ) {
128
145
const context = React . useContext ( GroupContext ) ;
129
146
if ( ! context ) {
130
147
throw new Error ( 'LineItems.Description must be used within LineItems.Group' ) ;
@@ -147,6 +164,7 @@ function Description({ text, prefix, suffix }: DescriptionProps) {
147
164
justifyContent : 'flex-end' ,
148
165
alignItems : 'center' ,
149
166
gap : t . space . $1 ,
167
+ minWidth : '0' ,
150
168
} ) }
151
169
>
152
170
{ prefix ? (
@@ -159,13 +177,27 @@ function Description({ text, prefix, suffix }: DescriptionProps) {
159
177
} ) }
160
178
/>
161
179
) : null }
162
- < Span
163
- localizationKey = { text }
164
- elementDescriptor = { descriptors . lineItemsDescriptionText }
165
- sx = { t => ( {
166
- ...common . textVariants ( t ) . body ,
167
- } ) }
168
- />
180
+ { typeof text === 'string' && truncateText ? (
181
+ < TruncatedText text = { text } />
182
+ ) : (
183
+ < Span
184
+ localizationKey = { text }
185
+ elementDescriptor = { descriptors . lineItemsDescriptionText }
186
+ sx = { t => ( {
187
+ ...common . textVariants ( t ) . body ,
188
+ minWidth : '0' ,
189
+ overflow : 'hidden' ,
190
+ textOverflow : 'ellipsis' ,
191
+ whiteSpace : 'nowrap' ,
192
+ } ) }
193
+ />
194
+ ) }
195
+ { typeof text === 'string' && copyText ? (
196
+ < CopyButton
197
+ text = { text }
198
+ copyLabel = { copyLabel }
199
+ />
200
+ ) : null }
169
201
</ Span >
170
202
{ suffix ? (
171
203
< Span
@@ -174,13 +206,64 @@ function Description({ text, prefix, suffix }: DescriptionProps) {
174
206
sx = { t => ( {
175
207
color : t . colors . $colorTextSecondary ,
176
208
...common . textVariants ( t ) . caption ,
209
+ justifySelf : 'flex-end' ,
177
210
} ) }
178
211
/>
179
212
) : null }
180
213
</ Dd >
181
214
) ;
182
215
}
183
216
217
+ function TruncatedText ( { text } : { text : string } ) {
218
+ const { onCopy } = useClipboard ( text ) ;
219
+ return (
220
+ < Span
221
+ elementDescriptor = { descriptors . lineItemsDescriptionText }
222
+ sx = { t => ( {
223
+ ...common . textVariants ( t ) . body ,
224
+ display : 'flex' ,
225
+ minWidth : '0' ,
226
+ } ) }
227
+ onCopy = { async e => {
228
+ e . preventDefault ( ) ;
229
+ await onCopy ( ) ;
230
+ } }
231
+ >
232
+ { truncateWithEndVisible ( text ) }
233
+ </ Span >
234
+ ) ;
235
+ }
236
+
237
+ function CopyButton ( { text, copyLabel = 'Copy' } : { text : string ; copyLabel ?: string } ) {
238
+ const { onCopy, hasCopied } = useClipboard ( text ) ;
239
+
240
+ return (
241
+ < Button
242
+ variant = 'unstyled'
243
+ onClick = { onCopy }
244
+ sx = { t => ( {
245
+ color : 'inherit' ,
246
+ width : t . sizes . $4 ,
247
+ height : t . sizes . $4 ,
248
+ padding : 0 ,
249
+ borderRadius : t . radii . $sm ,
250
+ '&:focus-visible' : {
251
+ outline : '2px solid' ,
252
+ outlineColor : t . colors . $neutralAlpha200 ,
253
+ } ,
254
+ } ) }
255
+ focusRing = { false }
256
+ aria-label = { hasCopied ? 'Copied' : copyLabel }
257
+ >
258
+ < Icon
259
+ size = 'sm'
260
+ icon = { hasCopied ? Check : Copy }
261
+ aria-hidden
262
+ />
263
+ </ Button >
264
+ ) ;
265
+ }
266
+
184
267
export const LineItems = {
185
268
Root,
186
269
Group,
0 commit comments