@hubspot/ui-extensions 0.0.0 → 0.0.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,1813 @@
1
+ # UI Extensions
2
+
3
+ React components and utilities for extending HubSpot's UI.
4
+
5
+ ## TOC
6
+
7
+ - [Alert](#alert)
8
+ - [Button](#button)
9
+ - [ButtonRow](#buttonrow)
10
+ - [Card](#card)
11
+ - [DescriptionList](#descriptionlist)
12
+ - [DescriptionListItem](#descriptionlistitem)
13
+ - [Divider](#divider)
14
+ - [Form](#form)
15
+ - [EmptyState](#emptystate)
16
+ - [ErrorState](#errorstate)
17
+ - [Heading](#heading)
18
+ - [Image](#image)
19
+ - [Input](#input)
20
+ - [Link](#link)
21
+ - [LoadingSpinner](#loadingspinner)
22
+ - [NumberInput](#numberInput)
23
+ - [ProgressBar](#progressbar)
24
+ - [Select](#select)
25
+ - [Stack](#stack)
26
+ - [Statistics](#statistics)
27
+ - [StatisticsItem](#statisticsitem)
28
+ - [StatisticsTrend](#statisticstrend)
29
+ - [Table](#table)
30
+ - [TableBody](#tablebody)
31
+ - [TableCell](#tablecell)
32
+ - [TableFooter](#tablefooter)
33
+ - [TableHead](#tablehead)
34
+ - [TableHeader](#tableheader)
35
+ - [TableRow](#tablerow)
36
+ - [Tag](#tag)
37
+ - [Text](#text)
38
+ - [Textarea](#textarea)
39
+ - [Tile](#tile)
40
+ - [ToggleGroup](#togglegroup)
41
+
42
+ ## Components
43
+
44
+ ### Alert
45
+
46
+ ##### Import
47
+
48
+ ```javascript
49
+ import { Alert } from '@hubspot/ui-extensions';
50
+ ```
51
+
52
+ ##### Props
53
+
54
+ The Alert component accepts the following props:
55
+
56
+ ```typescript
57
+ export interface AlertProps {
58
+ title: string;
59
+ children?: ReactNode;
60
+ variant?: 'info' | 'warning' | 'success' | 'error' | 'danger';
61
+ }
62
+ ```
63
+
64
+ | Prop | Type | Default | Description |
65
+ | --- | --- | --- | --- |
66
+ | `title` | `string` | `N/A` | The title text for the alert message. |
67
+ | `children` | `ReactNode(optional)` | `N/A` | The main content of the alert message when the body prop is not provided. |
68
+ | `variant` | `'info' \| 'warning' \| 'success' \|'error' \| 'danger'` `(optional)` | `'info'` | Sets the color variation of the alert |
69
+
70
+ ##### Usage
71
+
72
+ ```javascript
73
+ const Extension = () => {
74
+ return (
75
+ <>
76
+ <Alert title="Important Info" variant="info">
77
+ This is an informative message.
78
+ </Alert>
79
+ <Alert title="Success"variant="success">
80
+ Operation completed successfully.
81
+ </Alert>
82
+ <Alert title="Warning" variant="warning" >
83
+ Proceed with caution.
84
+ </Alert>
85
+ <Alert title="Error" variant="error" >
86
+ Something went wrong. Please try again.
87
+ </Alert>
88
+ <Alert title="Danger" variant="danger" >
89
+ This action cannot be undone. Be careful.
90
+ </Alert>
91
+ </>
92
+ );
93
+ };
94
+ ```
95
+
96
+ ### Button
97
+
98
+ ##### Import
99
+
100
+ ```javascript
101
+ import { Button } from '@hubspot/ui-extensions';
102
+ ```
103
+
104
+ ##### Props
105
+
106
+ ```typescript
107
+ interface ButtonProps {
108
+ children: string;
109
+ onClick?: () => void;
110
+ href?: string;
111
+ disabled?: boolean;
112
+ variant?: 'primary' | 'secondary' | 'destructive';
113
+ type?: 'button' | 'reset' | 'submit';
114
+ }
115
+ ```
116
+
117
+ | Prop | Type | Default | Description |
118
+ | --- | --- | --- | --- |
119
+ | `children` | `string` | `N/A` | The displayable text for the Button |
120
+ | `onClick` | `() => void` `(optional)` | `N/A` | A function that will be invoked when the button is clicked. It receives no arguments and it's return value is ignored |
121
+ | `href` | `string(optional)` | `N/A` | A URL that will be opened when the button is clicked. If the value is a URL external to HubSpot it will be opened in a new tab. |
122
+ | `disabled` | `boolean(optional)` | `N/A` | Determines if the button should be disabled or not. |
123
+ | `variant` | `'primary' \| 'secondary' \| 'destructive'` `(optional)` | `'secondary'` | Sets the color variation of the button |
124
+ | `type` | `'button' \| 'reset' \| 'submit'` `(optional)` | `'button'` | Sets the HTML attribute "role" of the button |
125
+
126
+ ##### Usage
127
+
128
+ ```javascript
129
+ const Extension = () => {
130
+ return (
131
+ <Button
132
+ onClick={() => {
133
+ console.log('Someone clicked on the button!');
134
+ }}
135
+ href="https://hubspot.com"
136
+ variant="destructive"
137
+ type="button"
138
+ >
139
+ Click me!
140
+ </Button>
141
+ );
142
+ };
143
+ ```
144
+
145
+ ### ButtonRow
146
+
147
+ ##### Import
148
+
149
+ ```javascript
150
+ import { ButtonRow } from '@hubspot/ui-extensions';
151
+ ```
152
+
153
+ ##### Props
154
+
155
+ ```typescript
156
+ interface ButtonRowProps {
157
+ children: ReactNode;
158
+ disableDropdown?: boolean;
159
+ }
160
+ ```
161
+
162
+ | Prop | Type | Default | Description |
163
+ | --- | --- | --- | --- |
164
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. |
165
+ | `disableDropdown` | `boolean(optional)` | `false` | Disables the dropdown list of buttons that appears the the children expand beyond the horizontal space |
166
+
167
+ ##### Usage
168
+
169
+ ```javascript
170
+ const Extension = () => {
171
+ return (
172
+ <ButtonRow disableDropdown={false}>
173
+ <Button
174
+ onClick={() => {
175
+ console.log('Regular button clicked');
176
+ }}
177
+ >
178
+ Regular Button
179
+ </Button>
180
+ <Button
181
+ onClick={() => {
182
+ console.log('Reset button clicked');
183
+ }}
184
+ variant="destructive"
185
+ type="reset"
186
+ >
187
+ Reset
188
+ </Button>
189
+ <Button
190
+ onClick={() => {
191
+ console.log('Submit button clicked');
192
+ }}
193
+ variant="primary"
194
+ type="submit"
195
+ >
196
+ Submit
197
+ </Button>
198
+ </ButtonRow>
199
+ );
200
+ };
201
+ ```
202
+
203
+ ### Card
204
+
205
+ ##### Import
206
+
207
+ ```javascript
208
+ import { Card } from '@hubspot/ui-extensions';
209
+ ```
210
+
211
+ ##### Props
212
+
213
+ ```typescript
214
+ interface CardProps {
215
+ children: ReactNode;
216
+ }
217
+ ```
218
+
219
+ | Prop | Type | Default | Description |
220
+ | --- | --- | --- | --- |
221
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. |
222
+
223
+ ##### Usage
224
+
225
+ ```javascript
226
+ const Extension = () => {
227
+ return (
228
+ <Card>
229
+ <Button
230
+ onClick={() => {
231
+ console.log('Regular button clicked');
232
+ }}
233
+ >
234
+ Regular Button
235
+ </Button>
236
+ </Card>
237
+ );
238
+ };
239
+ ```
240
+
241
+ ### DescriptionList
242
+
243
+ ##### Import
244
+
245
+ ```javascript
246
+ import { DescriptionList } from '@hubspot/ui-extensions';
247
+ ```
248
+
249
+ ##### Props
250
+
251
+ ```typescript
252
+ interface DescriptionListProps {
253
+ children: ReactNode;
254
+ direction?: 'row' | 'column';
255
+ }
256
+ ```
257
+
258
+ | Prop | Type | Default | Description |
259
+ | --- | --- | --- | --- |
260
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. The children should be [DescriptionListItem](#descriptionlistitem) |
261
+ | `direction` | `'row' \| 'column'` `(optional)` | `'column'` | The direction the label/value pairs are placed in the description list container. |
262
+
263
+ ##### Usage
264
+
265
+ ```javascript
266
+ const Extension = () => {
267
+ return (
268
+ <DescriptionList direction="row">
269
+ <DescriptionListItem label={'First Name'}>
270
+ <Text>Alan</Text>
271
+ </DescriptionListItem>
272
+ <DescriptionListItem label={'Last Name'}>
273
+ <Text>Turing</Text>
274
+ </DescriptionListItem>
275
+ </DescriptionList>
276
+ );
277
+ };
278
+ ```
279
+
280
+ #### DescriptionListItem
281
+
282
+ ##### Import
283
+
284
+ ```javascript
285
+ import { DescriptionListItem } from '@hubspot/ui-extensions';
286
+ ```
287
+
288
+ ##### Props
289
+
290
+ ```typescript
291
+ interface DescriptionListItemProps {
292
+ children: ReactNode;
293
+ label: string;
294
+ }
295
+ ```
296
+
297
+ | Prop | Type | Default | Description |
298
+ | --- | --- | --- | --- |
299
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. |
300
+ | `label` | `string` | `N/A` | Text to be displayed as the label |
301
+
302
+ ##### Usage
303
+
304
+ ```javascript
305
+ const Extension = () => {
306
+ return (
307
+ <DescriptionList direction="row">
308
+ <DescriptionListItem label={'First Name'}>
309
+ <Text>Alan</Text>
310
+ </DescriptionListItem>
311
+ <DescriptionListItem label={'Last Name'}>
312
+ <Text>Turing</Text>
313
+ </DescriptionListItem>
314
+ </DescriptionList>
315
+ );
316
+ };
317
+ ```
318
+
319
+ ### Divider
320
+
321
+ ##### Import
322
+
323
+ ```javascript
324
+ import { Divider } from '@hubspot/ui-extensions';
325
+ ```
326
+
327
+ ##### Props
328
+
329
+ ```typescript
330
+ interface DividerProps {
331
+ distance?:
332
+ | 'flush'
333
+ | 'extra-small'
334
+ | 'small'
335
+ | 'medium'
336
+ | 'large'
337
+ | 'extra-large';
338
+ }
339
+ ```
340
+
341
+ | Prop | Type | Default | Description |
342
+ | --- | --- | --- | --- |
343
+ | `distance` | `'flush'\| 'extra-small' \| 'small' \| 'medium' \| 'large'\| 'extra-large'` `(optional)` | `'small'` | The size of the padding above and below the divider. |
344
+
345
+ ##### Usage
346
+
347
+ ```javascript
348
+ const Extension = () => {
349
+ return <Divider distance="extra-large" />;
350
+ };
351
+ ```
352
+
353
+ ### Form
354
+
355
+ ##### Import
356
+
357
+ ```javascript
358
+ import { Form } from '@hubspot/ui-extensions';
359
+ ```
360
+
361
+ ##### Props
362
+
363
+ ```typescript
364
+ interface FormProps {
365
+ children: ReactNode;
366
+ onSubmit?: (event: RemoteEvent<FormInputValues>) => void;
367
+ preventDefault?: boolean;
368
+ }
369
+ ```
370
+
371
+ | Prop | Type | Default | Description |
372
+ | --- | --- | --- | --- |
373
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. |
374
+ | `onSubmit` | `function(optional)` | `N/A` | A function that will be called when the form is submitted. It will receive a `RemoteEvent` as argument and it's return value will be ignored. |
375
+ | `preventDefault` | `boolean(optional)` | `false` | If set to `true` `event.preventDefault()` will be invoked before your `onSubmit` function is called, preventing the default html form behavior. |
376
+
377
+ ##### Usage
378
+
379
+ ```javascript
380
+ const Extension = () => {
381
+ return (
382
+ <Form onSubmit={() => { console.log('Form submitted!')}} preventDefault={true}>
383
+ <Input {...} />
384
+ <Select {...} />
385
+ <Button
386
+ onClick={() => {
387
+ console.log('Submit button clicked');
388
+ }}
389
+ variant="primary"
390
+ type="submit"
391
+ >
392
+ Submit
393
+ </Button>
394
+ </Form>
395
+ );
396
+ }
397
+ ```
398
+
399
+ ### EmptyState
400
+
401
+ ##### Import
402
+
403
+ ```javascript
404
+ import { EmptyState } from '@hubspot/ui-extensions';
405
+ ```
406
+
407
+ ##### Props
408
+
409
+ ```typescript
410
+ interface EmptyStateProps {
411
+ children: ReactNode;
412
+ flush?: boolean;
413
+ imageWidth?: number;
414
+ layout?: 'horizontal' | 'vertical';
415
+ reverseOrder?: boolean;
416
+ title?: string;
417
+ }
418
+ ```
419
+
420
+ | Prop | Type | Default | Description |
421
+ | --- | --- | --- | --- |
422
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. |
423
+ | `flush` | `boolean(optional)` | `false` | Removes the default vertical margins for the component. |
424
+ | `imageWidth` | `number(optional)` | `250` | The max-width for the image container. |
425
+ | `layout` | `'horizontal' \| 'vertical'` `(optional)` | `'horizontal'` | Sets the layout direction for the content. |
426
+ | `reverseOrder` | `boolean(optional)` | `false` | Swaps the visual order of the text (primary) and image (secondary) content. This ensures the primary content is still presented first to assistive technology. |
427
+ | `title` | `string(optional)` | `Intl('All is not lost.')` | The text for the title header rendered above the `children`. |
428
+
429
+ ##### Usage
430
+
431
+ ```javascript
432
+ const Extension = ({ data }) => {
433
+ if (!data || !data.length) {
434
+ return (
435
+ <EmptyState title="Nothing here yet" layout="vertical" reverseOrder={true}>
436
+ <Text>Go out there and get some leads!</Text>
437
+ </EmptyState>
438
+ )
439
+ }
440
+
441
+ return (
442
+ <Stack>
443
+ {data.map(...)}
444
+ </Stack>
445
+ );
446
+ }
447
+ ```
448
+
449
+ ### ErrorState
450
+
451
+ ##### Import
452
+
453
+ ```javascript
454
+ import { ErrorState } from '@hubspot/ui-extensions';
455
+ ```
456
+
457
+ ##### Props
458
+
459
+ ```typescript
460
+ interface ErrorStateProps {
461
+ children: ReactNode;
462
+ title?: string;
463
+ type?: 'error' | 'support' | 'lock';
464
+ }
465
+ ```
466
+
467
+ | Prop | Type | Default | Description |
468
+ | --- | --- | --- | --- |
469
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. |
470
+ | `title` | `string(optional)` | `Intl('All is not lost.')` | The text for the title header rendered above the `children`. |
471
+ | `type` | `'error' \| 'support' \| 'lock'` `(optional)` | `'error'` | Sets the type of error image that will be shown. |
472
+
473
+ ##### Usage
474
+
475
+ ```javascript
476
+ const Extension = ({ data, error, fetchData }) => {
477
+ if (error) {
478
+ return (
479
+ <ErrorState title="Trouble fetching properties." layout="vertical" reverseOrder={true}>
480
+ <Stack>
481
+ <Text>
482
+ Please try again in a few moments.
483
+ </Text>
484
+ <Button onClick={fetchData}>
485
+ Try again
486
+ </Button>
487
+ </Stack>
488
+ </ErrorState>
489
+ )
490
+ }
491
+
492
+ return (
493
+ <Stack>
494
+ {data.map(...)}
495
+ </Stack>
496
+ );
497
+ }
498
+ ```
499
+
500
+ ### Heading
501
+
502
+ ##### Import
503
+
504
+ ```javascript
505
+ import { Heading } from '@hubspot/ui-extensions';
506
+ ```
507
+
508
+ ##### Props
509
+
510
+ ```typescript
511
+ interface HeadingProps {
512
+ children: ReactNode;
513
+ inline?: boolean;
514
+ }
515
+ ```
516
+
517
+ | Prop | Type | Default | Description |
518
+ | --- | --- | --- | --- |
519
+ | `children` | `string` | `N/A` | Text to be displayed as heading text. |
520
+ | `inline` | `boolean(optional)` | `false` | Determines if the text will break line or share the space. |
521
+
522
+ ##### Usage
523
+
524
+ ```javascript
525
+ const Extension = () => {
526
+ return <Heading>Plain text, nothing special here</Heading>;
527
+ };
528
+ ```
529
+
530
+ ### Image
531
+
532
+ ##### Import
533
+
534
+ ```javascript
535
+ import { Image } from '@hubspot/ui-extensions';
536
+ ```
537
+
538
+ ##### Props
539
+
540
+ ```typescript
541
+ interface ImageProps {
542
+ alt?: string;
543
+ href?: string;
544
+ onClick?: () => void;
545
+ src: string;
546
+ width?: number;
547
+ }
548
+ ```
549
+
550
+ | Prop | Type | Description |
551
+ | --- | --- | --- |
552
+ | `alt` | `string(optional)` | The alt text for the image, similar to the `alt` attribute for the html [img tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes) |
553
+ | `href` | `string(optional)` | If provided, it will be used as a href that will be opened in a new tag when the image is clicked |
554
+ | `onClick` | `function(optional)` | A function that will be called when the image is clicked on. This function will receive no arguments any returned values will be ignored. |
555
+ | `src` | `string` | The url to the image to display, similar to [img tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes) |
556
+ | `width` | `number(optional)` | The pixel width of the image |
557
+
558
+ ##### Usage
559
+
560
+ ```javascript
561
+ const Extension = () => {
562
+ return (
563
+ <Image
564
+ alt="A picture of an adorable black lab puppy, click on me to see in a new tab"
565
+ src="https://picsum.photos/id/237/200/300"
566
+ href="https://picsum.photos/id/237"
567
+ onClick={() => {
568
+ console.log('Someone clicked on the image!');
569
+ }}
570
+ width={200}
571
+ />
572
+ );
573
+ };
574
+ ```
575
+
576
+ ### Input
577
+
578
+ ##### Import
579
+
580
+ ```javascript
581
+ import { Input } from '@hubspot/ui-extensions';
582
+ ```
583
+
584
+ ##### Props
585
+
586
+ ```typescript
587
+ interface InputProps {
588
+ label: string;
589
+ name: string;
590
+ value?: string;
591
+ required?: boolean;
592
+ readOnly?: boolean;
593
+ description?: string;
594
+ tooltip?: string;
595
+ placeholder?: string;
596
+ error?: boolean;
597
+ validationMessage?: string;
598
+ onChange?: (value: string) => void;
599
+ onInput?: (value: string) => void;
600
+ onBlur?: (value: string) => void;
601
+ onFocus?: (value: string) => void;
602
+ }
603
+ ```
604
+
605
+ | Prop | Type | Default | Description |
606
+ | --- | --- | --- | --- |
607
+ | `label` | `string` | `N/A` | The label text to display for the form input element |
608
+ | `name` | `string` | `N/A` | The unique identifier for the input element, this could be thought of as the HTML5 [Input element's name attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#name) |
609
+ | `value` | `string(optional)` | `''` | The value of the input |
610
+ | `required` | `boolean(optional)` | `false` | Determines if the required indicator should be displayed |
611
+ | `readOnly` | `boolean(optional)` | `false` | Determines if the field is editable or not. |
612
+ | `description` | `string(optional)` | `N/A` | Instructional message to display to the user to help understand the purpose of the input. |
613
+ | `tooltip` | `string(optional)` | `N/A` | Text that will appear in a tooltip next to the input label. |
614
+ | `placeholder` | `string(optional)` | `N/A` | Text that appears in the input when it has no value set. |
615
+ | `error` | `boolean(optional)` | `false` | If set to true, `validationMessage` is displayed as an error message, if it was provided. The input will also render it's error state to let the user know there is an error. If false, `validationMessage` is displayed as a success message. |
616
+ | `validationMessage` | `string(optional)` | `''` | The text to show if the input has an error. |
617
+ | `onChange` | `(value: string) => void(optional)` | `N/A` | A callback function that is invoked when the value is committed. Currently these times are `onBlur` of the input and when the user submits the form. |
618
+ | `onInput` | `(value: string) => void(optional)` | `N/A` | A function that is called and passed the value every time the field is edited by the user. It is recommended that you do not use this value to update state, that is what `onChange` should be used for. Instead this should be used for validation. |
619
+ | `onBlur` | `(value: string) => void(optional)` | `N/A` | A function that is called and passed the value every time the field loses focus. |
620
+ | `onFocus` | `(value: string) => void(optional)` | `N/A` | A function that is called and passed the value every time the field gets focused. |
621
+
622
+ ##### Usage
623
+
624
+ ```javascript
625
+ import { useState } from 'react';
626
+
627
+ const Extension = () => {
628
+ const [name, setName] = useState('');
629
+ const [validationMessage, setValidationMessage] = useState('');
630
+ const [isValid, setIsValid] = useState(true);
631
+
632
+ return (
633
+ <Form>
634
+ <Input
635
+ label="First Name"
636
+ name="first-name"
637
+ tooltip="Please enter your first name"
638
+ description="Please enter your first name"
639
+ placeholder="First name"
640
+ required={true}
641
+ error={!isValid}
642
+ validationMessage={validationMessage}
643
+ onChange={value => {
644
+ setName(value);
645
+ }}
646
+ onInput={value => {
647
+ if (value !== 'Bill') {
648
+ setValidationMessage('This form only works for people named Bill');
649
+ setIsValid(false);
650
+ } else if (value === '') {
651
+ setValidationMessage('First name is required');
652
+ setIsValid(false);
653
+ } else {
654
+ setValidationMessage('Valid first name!');
655
+ setIsValid(true);
656
+ }
657
+ }}
658
+ />
659
+ </Form>
660
+ );
661
+ };
662
+ ```
663
+
664
+ ### Link
665
+
666
+ ##### Import
667
+
668
+ ```javascript
669
+ import { Link } from '@hubspot/ui-extensions';
670
+ ```
671
+
672
+ ##### Props
673
+
674
+ ```typescript
675
+ export interface LinkProps {
676
+ href: string;
677
+ variant?: 'primary' | 'destructive' | 'light' | 'dark';
678
+ children: ReactNode;
679
+ }
680
+ ```
681
+
682
+ | Prop | Type | Default | Description |
683
+ | --- | --- | --- | --- |
684
+ | `href` | `string` | `N/A` | A URL that will be opened when the link is clicked. If the value is a URL external to HubSpot it will be opened in a new tab. |
685
+ | `variant` | `'primary' \| 'light' \| 'dark' \| 'destructive'` `(optional)` | `'primary'` | Sets the color variation of the link |
686
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. |
687
+
688
+ ##### Usage
689
+
690
+ ```javascript
691
+ const Extension = () => {
692
+ return <Link href="https://app.hubspot.com/">HubSpot</Link>;
693
+ };
694
+ ```
695
+
696
+ ### LoadingSpinner
697
+
698
+ ##### Import
699
+
700
+ ```javascript
701
+ import { LoadingSpinner } from '@hubspot/ui-extensions';
702
+ ```
703
+
704
+ ##### Props
705
+
706
+ ```typescript
707
+ export interface LoadingSpinnerProps {
708
+ label: string;
709
+ showLabel?: boolean;
710
+ size?: 'xs' | 'extra-small' | 'sm' | 'small' | 'md' | 'medium';
711
+ layout?: 'inline' | 'centered';
712
+ }
713
+ ```
714
+
715
+ | Prop | Type | Default | Description |
716
+ | --- | --- | --- | --- |
717
+ | `label` | `string` | `N/A` | The companion text for the loading spinner. |
718
+ | `showLabel` | `boolean(optional)` | `false` | if `true`, the label will be visible alongside the loading spinner. |
719
+ | `size` | `'xs' \| 'sm' \| 'md' \| 'extra-small' \| 'small' \| 'medium'` `(optional)` | `'sm'` | The size of the loading spinner icon. |
720
+ | `layout` | `'inline'\| 'centered'` `(optional)` | `N/A` | Use the `centered` option for layout as a convenience for the common pattern of filling the space of its parent. |
721
+
722
+ ##### Usage
723
+
724
+ ```javascript
725
+ const Extension = () => {
726
+ return <LoadingSpinner label="Loading..." />;
727
+ };
728
+ ```
729
+ ### NumberInput
730
+
731
+ ##### Import
732
+
733
+ ```javascript
734
+ import { NumberInput } from '@hubspot/ui-extensions';
735
+ ```
736
+
737
+ ##### Props
738
+
739
+ ```typescript
740
+ export interface NumberInputProps {
741
+ label: string;
742
+ name: string;
743
+ value?: number;
744
+ required?: boolean;
745
+ readOnly?: boolean;
746
+ description?: string;
747
+ tooltip?: string;
748
+ placeholder?: string;
749
+ error?: boolean;
750
+ defaultValue?: number;
751
+ validationMessage?: string;
752
+ onChange?: (value: number) => void;
753
+ onInput?: (value: number) => void;
754
+ onBlur?: (value: number) => void;
755
+ onFocus?: (value: number) => void;
756
+ min?: number;
757
+ max?: number;
758
+ precision?: number;
759
+ formatStyle?: 'decimal' | 'percentage';
760
+ }
761
+ ```
762
+
763
+ | Prop | Type | Default | Description |
764
+ | --- | --- | --- | --- |
765
+ | `label` | `string` | `N/A` | The label text to display for the form input element |
766
+ | `name` | `string` | `N/A` | The unique identifier for the input element, this could be thought of as the HTML5 [Input element's name attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#name) |
767
+ | `value` | `number(optional)` | `''` | The value of the input |
768
+ | `required` | `boolean(optional)` | `false` | Determines if the required indicator should be displayed |
769
+ | `readOnly` | `boolean(optional)` | `false` | Determines if the field is editable or not. |
770
+ | `description` | `string(optional)` | `N/A` | Instructional message to display to the user to help understand the purpose of the input. |
771
+ | `tooltip` | `string(optional)` | `N/A` | Text that will appear in a tooltip next to the input label. |
772
+ | `placeholder` | `string(optional)` | `N/A` | Text that appears in the input when it has no value set. |
773
+ | `error` | `boolean(optional)` | `false` | If set to true, `validationMessage` is displayed as an error message, if it was provided. The input will also render it's error state to let the user know there is an error. If false, `validationMessage` is displayed as a success message. |
774
+ | `validationMessage` | `string(optional)` | `''` | The text to show if the input has an error. |
775
+ | `onChange` | `(value: number) => void(optional)` | `N/A` | A callback function that is invoked when the value is committed. Currently these times are `onBlur` of the input and when the user submits the form. |
776
+ | `onInput` | `(value: number) => void(optional)` | `N/A` | A function that is called and passed the value every time the field is edited by the user. It is recommended that you do not use this value to update state, that is what `onChange` should be used for. Instead this should be used for validation. |
777
+ | `onBlur` | `(value: number) => void(optional)` | `N/A` | A function that is called and passed the value every time the field loses focus. |
778
+ | `onFocus` | `(value: number) => void(optional)` | `N/A` | A function that is called and passed the value every time the field gets focused. |
779
+ | `min` | `number(optional)` | `N/A` | Sets the lower bound of your input. |
780
+ | `max` | `number(optional)` | `N/A` | Sets the upper bound of your input. |
781
+ | `precision` | `number(optional)` | `N/A` | Represents the number of digits to the right of the decimal point. |
782
+ | `precision` | `number(optional)` | `N/A` | Represents the number of digits to the right of the decimal point. |
783
+ | `formatStyle` | `'decimal' \| 'percentage'` | `'decimal'` | Formats the input as a decimal point number or a percentage. |
784
+
785
+ ##### Usage
786
+
787
+ ```javascript
788
+ const Extension = () => {
789
+ const [portalCount, setPortalCount] = useState(0);
790
+ return (
791
+ <NumberInput
792
+ label={'HubSpot Portal Count'}
793
+ name="portalsNumber"
794
+ description={'Number of active portals'}
795
+ placeholder={'number of portals'}
796
+ value={portalCount}
797
+ onChange={value => setPortalCount(value)}
798
+ />
799
+ );
800
+ };
801
+ ```
802
+
803
+ ### ProgressBar
804
+
805
+ ##### Import
806
+
807
+ ```javascript
808
+ import { ProgressBar } from '@hubspot/ui-extensions';
809
+ ```
810
+
811
+ ##### Props
812
+
813
+ ```typescript
814
+ export interface ProgressBarProps {
815
+ title?: string;
816
+ showPercentage?: boolean;
817
+ value?: number;
818
+ valueMax?: number;
819
+ valueDescription?: string;
820
+ variant?: 'success' | 'danger' | 'warning';
821
+ }
822
+ ```
823
+
824
+ | Prop | Type | Default | Description |
825
+ | --- | --- | --- | --- |
826
+ | `title` | `string(optional)` | `N/A` | Text to be displayed in the progressbar title. |
827
+ | `showPercentage` | `boolean(optional)` | `false` | Toggles the display of the completion percentage. |
828
+ | `value` | `number(optional)` | `0` | The value of the progress indicator. |
829
+ | `valueMax` | `number(optional)` | `100` | The maximum value of the progress. |
830
+ | `valueDescription` | `string(optional)` | `N/A` | Text that explains the current state of the `value` prop. Renders to the right of `title`. **Example: "10,000 of 7,500"** |
831
+ | `variant` | `'success' \| 'danger' \| 'warning'` | `'success'` | The type of progressbar to display. Defaults to success. |
832
+
833
+ ##### Usage
834
+
835
+ ```javascript
836
+ const Extension = () => {
837
+ return (
838
+ <ProgressBar
839
+ variant="warning"
840
+ value={50}
841
+ valueMax={200}
842
+ showPercentage={true}
843
+ />
844
+ );
845
+ };
846
+ ```
847
+
848
+ ### Select
849
+
850
+ ##### Import
851
+
852
+ ```javascript
853
+ import { Select } from '@hubspot/ui-extensions';
854
+ ```
855
+
856
+ ##### Props
857
+
858
+ ```typescript
859
+ interface SelectProps {
860
+ label: string;
861
+ name: string;
862
+ value?: string | number | boolean;
863
+ required?: boolean;
864
+ readOnly?: boolean;
865
+ description?: string;
866
+ tooltip?: string;
867
+ placeholder?: string;
868
+ error?: boolean;
869
+ errorMessage?: string;
870
+ onChange: (value: string) => void;
871
+ options: {
872
+ label: string;
873
+ value: string | number | boolean;
874
+ }[];
875
+ }
876
+ ```
877
+
878
+ | Prop | Type | Default | Description |
879
+ | --- | --- | --- | --- |
880
+ | `label` | `string` | `N/A` | The label text to display for the select element |
881
+ | `name` | `string` | `N/A` | The unique identifier for the select element. |
882
+ | `value` | `string \| number \| boolean` | `''` | The value of the select input. |
883
+ | `required` | `boolean` | `false` | Determines if the required indicator should be displayed |
884
+ | `readOnly` | `boolean` | `false` | Determines if the field is editable or not. |
885
+ | `description` | `string` | `N/A` | Instructional message to display to the user to help understand the purpose of the input. |
886
+ | `tooltip` | `string` | `N/A` | Text that will appear in a tooltip next to the input label. |
887
+ | `placeholder` | `string` | `N/A` | Text that appears in the input when it has no value set. |
888
+ | `error` | `boolean(optional)` | `false` | If set to true, `validationMessage` is displayed as an error message, if it was provided. The input will also render it's error state to let the user know there is an error. If false, `validationMessage` is displayed as a success message. |
889
+ | `validationMessage` | `string(optional)` | `''` | The text to show if the input has an error. |
890
+ | `onChange` | `(value: string) => void` | `N/A` | Function that is called with the new value when it is updated. |
891
+ | `options` | `Array<{label: string; value: string \| number \| boolean}>` | `N/A` | Array of options to be displayed in the select. `label` will be used as the display text in the dropdown list and `value` should be a **unique** identifier. `value` is the data that will be submitted with the form. |
892
+
893
+ ##### Usage
894
+
895
+ ```javascript
896
+ const Extension = () => {
897
+ const [name, setName] = useState(null);
898
+ const [validationMessage, setValidationMessage] = useState('');
899
+ const [isValid, setIsValid] = useState(true);
900
+
901
+ const options = [
902
+ { label: 'Bill', value: 42 },
903
+ { label: 'Ted', value: 43 },
904
+ ];
905
+
906
+ return (
907
+ <Form>
908
+ <Select
909
+ label="Best Bill & Ted Character?"
910
+ name="best-char"
911
+ tooltip="Please choose"
912
+ description="Please choose"
913
+ placeholder="Bill or Ted?"
914
+ required={true}
915
+ error={!isValid}
916
+ validationMessage={validationMessage}
917
+ onChange={value => {
918
+ setName(value);
919
+ if (!value) {
920
+ setValidationMessage('This is required');
921
+ setIsValid(false);
922
+ } else {
923
+ setValidationMessage('Excellent!');
924
+ setIsValid(true);
925
+ }
926
+ }}
927
+ options={options}
928
+ />
929
+ </Form>
930
+ );
931
+ };
932
+ ```
933
+
934
+ ### Stack
935
+
936
+ ##### Import
937
+
938
+ ```javascript
939
+ import { Stack } from '@hubspot/ui-extensions';
940
+ ```
941
+
942
+ ##### Props
943
+
944
+ ```typescript
945
+ interface StackProps {
946
+ distance?:
947
+ | 'flush'
948
+ | 'small'
949
+ | 'extra-small'
950
+ | 'medium'
951
+ | 'large'
952
+ | 'extra-large'
953
+ | 'xs'
954
+ | 'sm'
955
+ | 'md'
956
+ | 'lg'
957
+ | 'xl';
958
+ direction?: 'row' | 'column';
959
+ children?: ReactNode;
960
+ }
961
+ ```
962
+
963
+ | Prop | Type | Default | Description |
964
+ | --- | --- | --- | --- |
965
+ | `distance` | `'flush' \| 'extra-small' \| 'small' \| 'medium' \| 'large' \| 'extra-large' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'small'` | Amount of space between each child component passed as children |
966
+ | `direction` | `'row' \| 'column'` | `'column'` | Stacks elements in the vertical or horizontal direction. |
967
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. |
968
+
969
+ ##### Usage
970
+
971
+ ```javascript
972
+ const Extension = () => {
973
+ return (
974
+ <Stack>
975
+ <Image {...} />
976
+ <Text {...} />
977
+ </Stack>
978
+ );
979
+ }
980
+ ```
981
+
982
+ ### Statistics
983
+
984
+ ##### Import
985
+
986
+ ```javascript
987
+ import { Statistics } from '@hubspot/ui-extensions';
988
+ ```
989
+
990
+ ##### Props
991
+
992
+ ```typescript
993
+ interface StatisticsProps {
994
+ children: ReactNode;
995
+ }
996
+ ```
997
+
998
+ | Prop | Type | Default | Description |
999
+ | --- | --- | --- | --- |
1000
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. These children should be [StatisticsItem](#statisticsitem) or [StatisticsTrend](#statisticstrend) |
1001
+
1002
+ ##### Usage
1003
+
1004
+ ```javascript
1005
+ const Extension = () => {
1006
+ return (
1007
+ <Statistics>
1008
+ <StatisticsItem {...} />
1009
+ <StatisticsTrend {...} />
1010
+ </Statistics>
1011
+ );
1012
+ }
1013
+ ```
1014
+
1015
+ #### StatisticsItem
1016
+
1017
+ ##### Import
1018
+
1019
+ ```javascript
1020
+ import { StatisticsItem } from '@hubspot/ui-extensions';
1021
+ ```
1022
+
1023
+ ##### Props
1024
+
1025
+ ```typescript
1026
+ interface StatisticsItemProps {
1027
+ id?: string;
1028
+ label: string;
1029
+ number: string;
1030
+ children: ReactNode;
1031
+ }
1032
+ ```
1033
+
1034
+ | Prop | Type | Default | Description |
1035
+ | --- | --- | --- | --- |
1036
+ | `id` | `string(optional)` | `N/A` | The unique identifier |
1037
+ | `label` | `string` | `N/A` | The text to be used as a label |
1038
+ | `number` | `string` | `N/A` | Fully formatted string to be displayed as the item's primary number |
1039
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. These children should be [Text](#text) or [StatisticsTrend](#statisticstrend) |
1040
+
1041
+ ##### Usage
1042
+
1043
+ ```javascript
1044
+ const Extension = () => {
1045
+ return (
1046
+ <Statistics>
1047
+ <StatisticsItem label="Sales" number={'30000'}>
1048
+ <Text>Big Numbers</Text>
1049
+ </StatisticsItem>
1050
+ </Statistics>
1051
+ );
1052
+ };
1053
+ ```
1054
+
1055
+ #### StatisticsTrend
1056
+
1057
+ ##### Import
1058
+
1059
+ ```javascript
1060
+ import { StatisticsTrend } from '@hubspot/ui-extensions';
1061
+ ```
1062
+
1063
+ ##### Props
1064
+
1065
+ ```typescript
1066
+ interface StatisticsTrendProps {
1067
+ value: string;
1068
+ direction: 'increase' | 'decrease';
1069
+ }
1070
+ ```
1071
+
1072
+ | Prop | Type | Default | Description |
1073
+ | --- | --- | --- | --- |
1074
+ | `value` | `string` | `N/A` | Formatted string to be displayed as trend value |
1075
+ | `direction` | `'increase' \| 'decrease'` | `N/A` | Direction in which the trend arrow should be displayed |
1076
+
1077
+ ##### Usage
1078
+
1079
+ ```javascript
1080
+ const Extension = () => {
1081
+ return (
1082
+ <Statistics>
1083
+ <StatisticsItem label="Item A Sales" number="10000">
1084
+ <StatisticsTrend direction="decrease" value="200%" />
1085
+ </StatisticsItem>
1086
+ <StatisticsItem label="Item B Sales" number="100000">
1087
+ <StatisticsTrend direction="increase" value="100%" />
1088
+ </StatisticsItem>
1089
+ </Statistics>
1090
+ );
1091
+ };
1092
+ ```
1093
+
1094
+ ### Table
1095
+
1096
+ ##### Import
1097
+
1098
+ ```javascript
1099
+ import { Table } from '@hubspot/ui-extensions';
1100
+ ```
1101
+
1102
+ ##### Props
1103
+
1104
+ ```typescript
1105
+ interface TableProps {
1106
+ children: ReactNode;
1107
+ flush?: boolean;
1108
+ bordered?: boolean;
1109
+ paginated?: boolean;
1110
+ // if paginated=true
1111
+ pageCount: number;
1112
+ onPageChange: (pageNumber: number) => void;
1113
+ showButtonLabels?: boolean;
1114
+ showFirstLastButtons?: boolean;
1115
+ maxVisiblePageButtons?: number;
1116
+ page?: number;
1117
+ }
1118
+ ```
1119
+
1120
+ | Prop | Type | Default | Description |
1121
+ | --- | --- | --- | --- |
1122
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. These children should be [TableHead](#tablehead), [TableFooter](#tablefooter), or [TableBody](#tablebody) |
1123
+ | `flush` | `boolean(optional)` | `false` | If true the table will not have bottom margin. |
1124
+ | `bordered` | `boolean(optional)` | `true` | If false the table will not haver borders around its content. |
1125
+ | `paginated` | `boolean(optional)` | `false` | If true, the table will display the paginator component and consumer will have to provide extra pagination props. |
1126
+
1127
+ **Props for paginated=true**
1128
+
1129
+ | Prop | Type | Default | Description |
1130
+ | --- | --- | --- | --- |
1131
+ | `pageCount` | `number` | `N/A` | The total number of pages available |
1132
+ | `onPageChange` | `onPageChange: (pageNumber: number) => void` | `N/A` | A function that will be invoked when the pagination button is clicked. It receives the new page number as argument. |
1133
+ | `showButtonLabels` | `boolean(optional)` | `true` | if `false`, it hides the text labels for the First/Prev/Next/Last buttons. The button labels will still be accessible for screen readers. |
1134
+ | `showFirstLastButtons` | `boolean(optional)` | `false` | if `true`, it displays the First page and Last page buttons. |
1135
+ | `maxVisiblePageButtons` | `number(optional)` | `5` | Changes how many page buttons are shown. |
1136
+ | `page` | `number(optional)` | `N/A` | Denotes the current page. |
1137
+
1138
+ ##### Usage
1139
+
1140
+ ```javascript
1141
+ const Extension = () => {
1142
+ return (
1143
+ <Table bordered={true}>
1144
+ <TableHead>
1145
+ <TableRow>
1146
+ <TableHeader>Name</TableHeader>
1147
+ <TableHeader>Phone</TableHeader>
1148
+ </TableRow>
1149
+ </TableHead>
1150
+ <TableBody>
1151
+ <TableRow>
1152
+ <TableCell>Roger Federer</TableCell>
1153
+ <TableCell>555-555-7866</TableCell>
1154
+ </TableRow>
1155
+ </TableBody>
1156
+ </Table>
1157
+ );
1158
+ };
1159
+
1160
+ // Paginated example
1161
+
1162
+ function generateData(count = 15) {
1163
+ const result = [];
1164
+
1165
+ for (let index = 0; index < count; index++) {
1166
+ result.push({
1167
+ name: `Jane Doe ${index}`,
1168
+ email: `janedoemail${index}@hubspot.com`,
1169
+ });
1170
+ }
1171
+
1172
+ return result;
1173
+ }
1174
+
1175
+ function PaginatedTable() {
1176
+ const ITEMS_PER_PAGE = 10;
1177
+ const data = generateData(30);
1178
+
1179
+ const [currentPage, setCurrentPage] = useState(1);
1180
+ const pageCount = data.length / ITEMS_PER_PAGE;
1181
+
1182
+ const dataToDisplay = data.slice(
1183
+ (currentPage - 1) * ITEMS_PER_PAGE,
1184
+ currentPage * ITEMS_PER_PAGE
1185
+ );
1186
+
1187
+ return (
1188
+ <Table
1189
+ paginated={true}
1190
+ pageCount={pageCount}
1191
+ page={currentPage}
1192
+ onPageChange={(nextPageNumber: number) => {
1193
+ setCurrentPage(nextPageNumber);
1194
+ }}
1195
+ >
1196
+ <TableHead>
1197
+ <TableRow>
1198
+ <TableHeader>Name</TableHeader>
1199
+ <TableHeader>Email</TableHeader>
1200
+ </TableRow>
1201
+ </TableHead>
1202
+ <TableBody>
1203
+ {dataToDisplay.map(({ name, email }) => {
1204
+ console.log(name, email);
1205
+ return (
1206
+ <TableRow key={email}>
1207
+ <TableCell>{name}</TableCell>
1208
+ <TableCell>{email}</TableCell>
1209
+ </TableRow>
1210
+ );
1211
+ })}
1212
+ </TableBody>
1213
+ </Table>
1214
+ );
1215
+ }
1216
+ ```
1217
+
1218
+ ### TableBody
1219
+
1220
+ ##### Import
1221
+
1222
+ ```javascript
1223
+ import { TableBody } from '@hubspot/ui-extensions';
1224
+ ```
1225
+
1226
+ ##### Props
1227
+
1228
+ ```typescript
1229
+ interface TableBodyProps {
1230
+ children: ReactNode;
1231
+ }
1232
+ ```
1233
+
1234
+ | Prop | Type | Default | Description |
1235
+ | --- | --- | --- | --- |
1236
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. These children should be [TableRow](#tablerow). |
1237
+
1238
+ ##### Usage
1239
+
1240
+ ```javascript
1241
+ const Extension = () => {
1242
+ return (
1243
+ <Table bordered={true}>
1244
+ <TableHead>
1245
+ <TableRow>
1246
+ <TableHeader>Name</TableHeader>
1247
+ <TableHeader>Phone</TableHeader>
1248
+ </TableRow>
1249
+ </TableHead>
1250
+ <TableBody>
1251
+ <TableRow>
1252
+ <TableCell>Roger Federer</TableCell>
1253
+ <TableCell>555-555-7866</TableCell>
1254
+ </TableRow>
1255
+ </TableBody>
1256
+ </Table>
1257
+ );
1258
+ };
1259
+ ```
1260
+
1261
+ ### TableCell
1262
+
1263
+ ##### Import
1264
+
1265
+ ```javascript
1266
+ import { TableCell } from '@hubspot/ui-extensions';
1267
+ ```
1268
+
1269
+ ##### Props
1270
+
1271
+ ```typescript
1272
+ interface TableCellProps {
1273
+ children: ReactNode;
1274
+ }
1275
+ ```
1276
+
1277
+ | Prop | Type | Default | Description |
1278
+ | --- | --- | --- | --- |
1279
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. |
1280
+
1281
+ ##### Usage
1282
+
1283
+ ```javascript
1284
+ const Extension = () => {
1285
+ return (
1286
+ <Table bordered={true}>
1287
+ <TableHead>
1288
+ <TableRow>
1289
+ <TableHeader>Name</TableHeader>
1290
+ <TableHeader>Phone</TableHeader>
1291
+ </TableRow>
1292
+ </TableHead>
1293
+ <TableBody>
1294
+ <TableRow>
1295
+ <TableCell>Roger Federer</TableCell>
1296
+ <TableCell>555-555-7866</TableCell>
1297
+ </TableRow>
1298
+ </TableBody>
1299
+ </Table>
1300
+ );
1301
+ };
1302
+ ```
1303
+
1304
+ ### TableFooter
1305
+
1306
+ ##### Import
1307
+
1308
+ ```javascript
1309
+ import { TableFooter } from '@hubspot/ui-extensions';
1310
+ ```
1311
+
1312
+ ##### Props
1313
+
1314
+ ```typescript
1315
+ interface TableFooterProps {
1316
+ children: ReactNode;
1317
+ }
1318
+ ```
1319
+
1320
+ | Prop | Type | Default | Description |
1321
+ | --- | --- | --- | --- |
1322
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. These children should be [TableRow](#tablerow). |
1323
+
1324
+ ##### Usage
1325
+
1326
+ ```javascript
1327
+ const Extension = () => {
1328
+ return (
1329
+ <Table>
1330
+ <TableHead>
1331
+ <TableRow>
1332
+ <TableHeader>Series</TableHeader>
1333
+ <TableHeader>Years on air</TableHeader>
1334
+ <TableHeader>Emmys</TableHeader>
1335
+ </TableRow>
1336
+ </TableHead>
1337
+ <TableFooter>
1338
+ <TableRow>
1339
+ <TableHeader>Totals</TableHeader>
1340
+ <TableHeader>43</TableHeader>
1341
+ <TableHeader>50</TableHeader>
1342
+ </TableRow>
1343
+ </TableFooter>
1344
+ <TableBody>
1345
+ <TableRow>
1346
+ <TableCell>The Simpsons</TableCell>
1347
+ <TableCell>28</TableCell>
1348
+ <TableCell>31</TableCell>
1349
+ </TableRow>
1350
+ <TableRow>
1351
+ <TableCell>M*A*S*H</TableCell>
1352
+ <TableCell>11</TableCell>
1353
+ <TableCell>14</TableCell>
1354
+ </TableRow>
1355
+ <TableRow>
1356
+ <TableCell>Arrested Development</TableCell>
1357
+ <TableCell>4</TableCell>
1358
+ <TableCell>5</TableCell>
1359
+ </TableRow>
1360
+ </TableBody>
1361
+ </Table>
1362
+ );
1363
+ };
1364
+ ```
1365
+
1366
+ ### TableHead
1367
+
1368
+ ##### Import
1369
+
1370
+ ```javascript
1371
+ import { TableHead } from '@hubspot/ui-extensions';
1372
+ ```
1373
+
1374
+ ##### Props
1375
+
1376
+ ```typescript
1377
+ interface TableHeadProps {
1378
+ children: ReactNode;
1379
+ }
1380
+ ```
1381
+
1382
+ | Prop | Type | Default | Description |
1383
+ | --- | --- | --- | --- |
1384
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. These children should be [TableHeader](#tableheader). |
1385
+
1386
+ ##### Usage
1387
+
1388
+ ```javascript
1389
+ const Extension = () => {
1390
+ return (
1391
+ <Table bordered={true}>
1392
+ <TableHead>
1393
+ <TableRow>
1394
+ <TableHeader>Name</TableHeader>
1395
+ <TableHeader>Phone</TableHeader>
1396
+ </TableRow>
1397
+ </TableHead>
1398
+ <TableBody>
1399
+ <TableRow>
1400
+ <TableCell>Roger Federer</TableCell>
1401
+ <TableCell>555-555-7866</TableCell>
1402
+ </TableRow>
1403
+ </TableBody>
1404
+ </Table>
1405
+ );
1406
+ };
1407
+ ```
1408
+
1409
+ ### TableHeader
1410
+
1411
+ ##### Import
1412
+
1413
+ ```javascript
1414
+ import { TableHeader } from '@hubspot/ui-extensions';
1415
+ ```
1416
+
1417
+ ##### Props
1418
+
1419
+ ```typescript
1420
+ interface TableHeaderProps {
1421
+ children: ReactNode;
1422
+ }
1423
+ ```
1424
+
1425
+ | Prop | Type | Default | Description |
1426
+ | --- | --- | --- | --- |
1427
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. |
1428
+
1429
+ ##### Usage
1430
+
1431
+ ```javascript
1432
+ const Extension = () => {
1433
+ return (
1434
+ <Table bordered={true}>
1435
+ <TableHead>
1436
+ <TableRow>
1437
+ <TableHeader>Name</TableHeader>
1438
+ <TableHeader>Phone</TableHeader>
1439
+ </TableRow>
1440
+ </TableHead>
1441
+ <TableBody>
1442
+ <TableRow>
1443
+ <TableCell>Roger Federer</TableCell>
1444
+ <TableCell>555-555-7866</TableCell>
1445
+ </TableRow>
1446
+ </TableBody>
1447
+ </Table>
1448
+ );
1449
+ };
1450
+ ```
1451
+
1452
+ ### TableRow
1453
+
1454
+ ##### Import
1455
+
1456
+ ```javascript
1457
+ import { TableRow } from '@hubspot/ui-extensions';
1458
+ ```
1459
+
1460
+ ##### Props
1461
+
1462
+ ```typescript
1463
+ interface TableRowProps {
1464
+ children: ReactNode;
1465
+ }
1466
+ ```
1467
+
1468
+ | Prop | Type | Default | Description |
1469
+ | --- | --- | --- | --- |
1470
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. |
1471
+
1472
+ ##### Usage
1473
+
1474
+ ```javascript
1475
+ const Extension = () => {
1476
+ return (
1477
+ <Table bordered={true}>
1478
+ <TableHead>
1479
+ <TableRow>
1480
+ <TableHeader>Name</TableHeader>
1481
+ <TableHeader>Phone</TableHeader>
1482
+ </TableRow>
1483
+ </TableHead>
1484
+ <TableBody>
1485
+ <TableRow>
1486
+ <TableCell>Roger Federer</TableCell>
1487
+ <TableCell>555-555-7866</TableCell>
1488
+ </TableRow>
1489
+ </TableBody>
1490
+ </Table>
1491
+ );
1492
+ };
1493
+ ```
1494
+
1495
+ ### Tag
1496
+
1497
+ ##### Import
1498
+
1499
+ ```javascript
1500
+ import { Tag } from '@hubspot/ui-extensions';
1501
+ ```
1502
+
1503
+ ##### Props
1504
+
1505
+ ```typescript
1506
+ interface TagProps {
1507
+ children: ReactNode;
1508
+ onClick?: () => void;
1509
+ variant?: 'default' | 'warning' | 'success' | 'error';
1510
+ }
1511
+ ```
1512
+
1513
+ | Prop | Type | Default | Description |
1514
+ | --- | --- | --- | --- |
1515
+ | `children` | `string` | `N/A` | Text to be displayed in the tag. |
1516
+ | `onClick` | `() => void` `(optional)` | `N/A` | A function that will be invoked when the `Tag` is clicked. It receives no arguments and it's return value is ignored. |
1517
+ | `variant` | `'default' \| 'warning' \| 'success' \| 'error'` `(optional)` | `'default'` | The color variation of the tag to display |
1518
+
1519
+ ##### Usage
1520
+
1521
+ ```javascript
1522
+ const Extension = () => {
1523
+ return (
1524
+ <Tag
1525
+ variant="success"
1526
+ onClick={() => {
1527
+ console.log('Tag clicked!');
1528
+ }}
1529
+ >
1530
+ Success
1531
+ </Tag>
1532
+ );
1533
+ };
1534
+ ```
1535
+
1536
+ ### Text
1537
+
1538
+ ##### Import
1539
+
1540
+ ```javascript
1541
+ import { Text } from '@hubspot/ui-extensions';
1542
+ ```
1543
+
1544
+ ##### Props
1545
+
1546
+ ```typescript
1547
+ export interface TextFormatOptions {
1548
+ fontWeight?: 'regular' | 'bold' | 'demibold';
1549
+ italic?: boolean;
1550
+ lineDecoration?: 'none' | 'underline' | 'strikethrough';
1551
+ }
1552
+
1553
+ export type TextProps = {
1554
+ variant?: 'bodytext' | 'microcopy';
1555
+ inline?: boolean;
1556
+ children: ReactNode;
1557
+ format?: TextFormatOptions;
1558
+ };
1559
+ ```
1560
+
1561
+ | Prop | Type | Default | Description |
1562
+ | --- | --- | --- | --- |
1563
+ | `format` | `'TextFormatOptions'` `(optional)` | `N/A` | Type of formatting options for the text. |
1564
+ | `children` | `string` | `N/A` | Text to be displayed as body text. |
1565
+ | `variant` | `'bodytext' \| 'microcopy'` | `'bodytext'` | Type of text to display |
1566
+ | `tagName` | `'p' \| 'small' \| 'span'` | `'bodytext'` | Type of text element(tag) to display. |
1567
+ | `inline` | `boolean(optional)` | `false` | If `false` the text component will not insert a line break. |
1568
+
1569
+ #### Format Options
1570
+
1571
+ - bold text: `{ fontWeight: 'bold' }`
1572
+ - semibold text: `{ fontWeight: 'demibold' }`
1573
+ - italicized text: `{ italic: true }`
1574
+ - strikethrough text: `{ lineDecoration: 'strikethrough' }`
1575
+ - underlined text: `{ lineDecoration: 'underline' }`
1576
+ - inline text: `<Text inline={true}/>`
1577
+
1578
+
1579
+ ##### Usage
1580
+
1581
+ ```javascript
1582
+ const Extension = () => {
1583
+ return (
1584
+ <>
1585
+ <Text>Plain text</Text>
1586
+ <Text format={{ fontWeight: 'bold' }}>Bold</Text>
1587
+ <Text format={{ italic: true }}>Italics</Text>
1588
+ <Text format={{ fontWeight: 'bold', italic: true }}>
1589
+ Bold and Italic text
1590
+ </Text>
1591
+ <Text format={{ lineDecoration: 'strikethrough' }}>
1592
+ Strikethrough Text
1593
+ </Text>
1594
+ <Text variant="microcopy">
1595
+ Microcopy text
1596
+ <Text inline={true} format={{ fontWeight: 'bold' }}>
1597
+ with inner bold
1598
+ </Text>
1599
+ </Text>
1600
+ </>
1601
+ );
1602
+ };
1603
+ ```
1604
+
1605
+ ### Textarea
1606
+
1607
+ ##### Import
1608
+
1609
+ ```javascript
1610
+ import { Textarea } from '@hubspot/ui-extensions';
1611
+ ```
1612
+
1613
+ ##### Props
1614
+
1615
+ ```typescript
1616
+ interface TextareaProps {
1617
+ label: string;
1618
+ name: string;
1619
+ value?: string;
1620
+ required?: boolean;
1621
+ readOnly?: boolean;
1622
+ description?: string;
1623
+ tooltip?: string;
1624
+ placeholder?: string;
1625
+ error?: boolean;
1626
+ validationMessage?: string;
1627
+ onChange?: (value: string) => void;
1628
+ onInput?: (value: string) => void;
1629
+ onBlur?: (value: string) => void;
1630
+ onFocus?: (value: string) => void;
1631
+ cols?: number;
1632
+ maxLength?: number;
1633
+ rows?: number;
1634
+ resize?: 'vertical' | 'horizontal' | 'both' | 'none';
1635
+ }
1636
+ ```
1637
+
1638
+ | Prop | Type | Default | Description |
1639
+ | --- | --- | --- | --- |
1640
+ | `label` | `string` | `N/A` | The label text to display for the textarea element |
1641
+ | `name` | `string` | `N/A` | The unique identifier for the textarea element, this could be thought of as the HTML5 [Textarea element's name attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#name) |
1642
+ | `value` | `string(optional)` | `''` | The value of the textarea |
1643
+ | `required` | `boolean(optional)` | `false` | Determines if the required indicator should be displayed |
1644
+ | `readOnly` | `boolean(optional)` | `false` | Determines if the field is editable or not. |
1645
+ | `description` | `string(optional)` | `N/A` | Instructional message to display to the user to help understand the purpose of the textarea. |
1646
+ | `tooltip` | `string(optional)` | `N/A` | Text that will appear in a tooltip next to the textarea label. |
1647
+ | `placeholder` | `string(optional)` | `N/A` | Text that appears in the textarea when it has no value set. |
1648
+ | `error` | `boolean(optional)` | `false` | If set to true, `validationMessage` is displayed as an error message, if it was provided. The textarea will also render it's error state to let the user know there is an error. If false, `validationMessage` is displayed as a success message. |
1649
+ | `validationMessage` | `string(optional)` | `''` | The text to show if the textarea has an error. |
1650
+ | `onChange` | `(value: string) => void(optional)` | `N/A` | A callback function that is invoked when the value is committed. Currently these times are `onBlur` of the textarea and when the user submits the form. |
1651
+ | `onInput` | `(value: string) => void(optional)` | `N/A` | A function that is called and passed the value every time the field is edited by the user. It is recommended that you do not use this value to update state, that is what `onChange` should be used for. Instead this should be used for validation. |
1652
+ | `onBlur` | `(value: string) => void(optional)` | `N/A` | A function that is called and passed the value every time the field loses focus. |
1653
+ | `onFocus` | `(value: string) => void(optional)` | `N/A` | A function that is called and passed the value every time the field gets focused. |
1654
+ | `cols` | `number(optional)` | `N/A` | The visible width of the text control, in average character widths. |
1655
+ | `rows` | `number(optional)` | `N/A` | The number of visible text lines for the control. |
1656
+ | `maxLength` | `number(optional)` | `N/A` | The maximum number of characters (UTF-16 code units) that the user can enter. If this value isn't specified, the user can enter an unlimited number of characters. |
1657
+ | `resize` | `'vertical' \| 'horizontal' \| 'both' \| 'none'` `(optional)` | `'vertical'` | Sets whether an element is resizable, and if so, in which directions. |
1658
+
1659
+ ##### Usage
1660
+
1661
+ ```javascript
1662
+ import { useState } from 'react';
1663
+
1664
+ const Extension = () => {
1665
+ const [desciptiption, setDescription] = useState('');
1666
+ const [validationMessage, setValidationMessage] = useState('');
1667
+ const [isValid, setIsValid] = useState(true);
1668
+
1669
+ return (
1670
+ <Form>
1671
+ <Textarea
1672
+ label="Description"
1673
+ name="description"
1674
+ tooltip="Provide as much detail as possible"
1675
+ description="Please include a link"
1676
+ placeholder="My desription"
1677
+ required={true}
1678
+ error={!isValid}
1679
+ validationMessage={validationMessage}
1680
+ onChange={value => {
1681
+ setDescription(value);
1682
+ }}
1683
+ onInput={value => {
1684
+ if (!value.includes('http')) {
1685
+ setValidationMessage('A link must be included.');
1686
+ setIsValid(false);
1687
+ } else {
1688
+ setValidationMessage('Valid description!');
1689
+ setIsValid(true);
1690
+ }
1691
+ }}
1692
+ />
1693
+ </Form>
1694
+ );
1695
+ };
1696
+ ```
1697
+
1698
+ ### Tile
1699
+
1700
+ ##### Import
1701
+
1702
+ ```javascript
1703
+ import { Tile } from '@hubspot/ui-extensions';
1704
+ ```
1705
+
1706
+ ##### Props
1707
+
1708
+ ```typescript
1709
+ interface TileProps {
1710
+ children: ReactNode;
1711
+ flush?: boolean;
1712
+ }
1713
+ ```
1714
+
1715
+ | Prop | Type | Default | Description |
1716
+ | --- | --- | --- | --- |
1717
+ | `children` | `ReactNode` | `N/A` | Sets the content that will render inside the component. This prop is passed implicitly by providing sub-components. |
1718
+ | `flush` | `boolean(optional)` | `false` | If true the content of the Tile will have no left or right padding. |
1719
+
1720
+ ##### Usage
1721
+
1722
+ ```javascript
1723
+ const Extension = () => {
1724
+ return (
1725
+ <>
1726
+ <Tile flush={true}>
1727
+ <Text>No left padding</Text>
1728
+ </Tile>
1729
+ <Tile>
1730
+ <Text>Small amount of left padding</Text>
1731
+ </Tile>
1732
+ </>
1733
+ );
1734
+ };
1735
+ ```
1736
+
1737
+ ### ToggleGroup
1738
+
1739
+ ##### Import
1740
+
1741
+ ```javascript
1742
+ import { ToggleGroup } from '@hubspot/ui-extensions';
1743
+ ```
1744
+
1745
+ ##### Props
1746
+
1747
+ ```typescript
1748
+ type ToggleGroupProps = {
1749
+ toggleType: 'checkboxList' | 'radioButtonList';
1750
+ name: string;
1751
+ label: string;
1752
+ value?: string | string[];
1753
+ onChange?: (value: string) => void | (value: string[]) => void;
1754
+ validationMessage?: string;
1755
+ required?: boolean;
1756
+ tooltip?: string;
1757
+ error?: boolean;
1758
+ options: {
1759
+ label: string;
1760
+ value: string;
1761
+ initialIsChecked?: boolean;
1762
+ readonly?: boolean;
1763
+ description?: string;
1764
+ }[];
1765
+ inline?: boolean;
1766
+ variant?: 'default' | 'small';
1767
+ };
1768
+
1769
+ ```
1770
+
1771
+ | Prop | Type | Default | Description |
1772
+ | --- | --- | --- | --- | --- |
1773
+ | `toggleType` | `'radioButtonList' \| 'checkboxList'` | `N/A` | The type of toggles that will be shown. `'checkboxList'` is a multi-select and `'radioButtonList'` is a single select | ; |
1774
+ | `name` | `string` | `N/A` | The unique identifier for the toggle group element. |
1775
+ | `label` | `string` | `N/A` | The label text to display for the toggle group element. |
1776
+ | `options` | `Array<{ label: string; value: string; initialIsChecked?: boolean; readonly?: boolean; description?: string }>` | `N/A` | |
1777
+ | `value` | `string \| string[]` `(optional)` | `N/A` | The value of the toggle group. If `toggleType` is `'radioButtonList'`, this should be a `string`. If `toggleType` is `'checkboxList'` this should be an array of `string`s. |
1778
+ | `onChange` | `(value: string) => void \| (value: string[]) => void` `(optional)` | `N/A` | A function that is called with the new value or values when it is updated. If `toggleType` is `'radioButtonList'`, the function will be called with the `value` that is a `string`. If `toggleType` is `'checkboxList'` the function will be called with the `value` that is an array of `string`s. |
1779
+ | `required` | `boolean(optional)` | `false` | Determines if the required indicator should be displayed |
1780
+ | `tooltip` | `string` | `N/A` | Text that will appear in a tooltip next to the toggle group label. |
1781
+ | `error` | `boolean(optional)` | `false` | If set to true, `validationMessage` is displayed as an error message, if it was provided. The input will also render it's error state to let the user know there is an error. If false, `validationMessage` is displayed as a success message. |
1782
+ | `validationMessage` | `string(optional)` | `''` | The text to show if the input has an error. |
1783
+ | `inline` | `boolean(optional)` | `false` | Determines of the options are stacked vertically or share a line horizontally. |
1784
+ | `variant` | `'default' \| 'small'` `(optional)` | `default` | The size variation of the individual options. |
1785
+
1786
+ ##### Usage
1787
+
1788
+ ```javascript
1789
+ const options = [1, 2, 3, 4].map(n => ({
1790
+ label: `Option ${n}`,
1791
+ value: `${n}`,
1792
+ initialIsChecked: n === 2,
1793
+ readonly: false,
1794
+ description: `This is option ${n}`,
1795
+ }));
1796
+
1797
+ const Extension = () => {
1798
+ return (
1799
+ <ToggleGroup
1800
+ name="toggle-checkboxes"
1801
+ label="Toggle these things"
1802
+ error={false}
1803
+ options={options}
1804
+ tooltip="Here's a secret tip."
1805
+ validationMessage="Make sure you do the thing correctly."
1806
+ required={false}
1807
+ inline={false}
1808
+ toggleType="checkboxList"
1809
+ variant="default"
1810
+ />
1811
+ );
1812
+ };
1813
+ ```