Repository: vmware-tanzu/octant
Pull request #2334 (2021-04-19 05:26:02)

Bump @types/node from 14.14.35 to 14.14.41 in /web
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.14.35 to 14.14.41.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@types/node&package-manager=npm_and_yarn&previous-version=14.14.35&new-version=14.14.41)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/changelogs/unreleased/2306-ftovaro b/changelogs/unreleased/2306-ftovaro
new file mode 100644
index 00000000..63b5d30e
--- /dev/null
+++ b/changelogs/unreleased/2306-ftovaro
@@ -0,0 +1,1 @@
+Added styling for Buttons
diff --git a/pkg/view/component/button.go b/pkg/view/component/button.go
index 4c3bf339..9eedafda 100644
--- a/pkg/view/component/button.go
+++ b/pkg/view/component/button.go
@@ -12,6 +12,63 @@ import (
 	"github.com/vmware-tanzu/octant/pkg/action"
 )
 
+const (
+	// ButtonStatusSuccess is a green button
+	ButtonStatusSuccess ButtonStatus = "success"
+	// ButtonStatusInfo is the default blue color
+	ButtonStatusInfo ButtonStatus = "primary"
+	// ButtonStatusDanger is a red button
+	ButtonStatusDanger ButtonStatus = "danger"
+	// ButtonStatusDisabled is a disabled button
+	ButtonStatusDisabled ButtonStatus = "disabled"
+)
+
+// ButtonStatus is the status of a Button
+type ButtonStatus string
+
+const (
+	// ButtonSizeBlock is a full width button
+	ButtonSizeBlock ButtonSize = "block"
+	// ButtonSizeLarge is a small button
+	ButtonSizeMedium ButtonSize = "md"
+)
+
+// ButtonSize defines the size of a Button
+type ButtonSize string
+
+const (
+	// ButtonStyleOutline is a transparent button with a colored border
+	ButtonStyleOutline ButtonStyle = "outline"
+	// ButtonStyleSolid is a button with solid color
+	ButtonStyleSolid ButtonStyle = "solid"
+	// ButtonStyleFlat is a button with no background color or outline
+	ButtonStyleFlat ButtonStyle = "flat"
+)
+
+// ButtonStyle is the style of a Button
+type ButtonStyle string
+
+// WithButtonStatus configures the button color
+func WithButtonStatus(status ButtonStatus) ButtonOption {
+	return func(button *Button) {
+		button.Config.Status = status
+	}
+}
+
+// WithButtonSize configures the button size
+func WithButtonSize(size ButtonSize) ButtonOption {
+	return func(button *Button) {
+		button.Config.Size = size
+	}
+}
+
+// WithButtonStyle configures the button appearance
+func WithButtonStyle(style ButtonStyle) ButtonOption {
+	return func(button *Button) {
+		button.Config.Style = style
+	}
+}
+
 // Confirmation is configuration for a confirmation dialog.
 type Confirmation struct {
 	Title string `json:"title"`
@@ -48,6 +105,9 @@ func (b *ButtonConfig) UnmarshalJSON(data []byte) error {
 		Payload      action.Payload `json:"payload"`
 		Confirmation *Confirmation  `json:"confirmation,omitempty"`
 		Modal        *TypedObject   `json:"modal,omitempty"`
+		Status       ButtonStatus   `json:"status,omitempty"`
+		Size         ButtonSize     `json:"size,omitempty"`
+		Style        ButtonStyle    `json:"style,omitempty"`
 	}{}
 
 	if err := json.Unmarshal(data, &x); err != nil {
@@ -70,6 +130,9 @@ func (b *ButtonConfig) UnmarshalJSON(data []byte) error {
 	b.Name = x.Name
 	b.Payload = x.Payload
 	b.Confirmation = x.Confirmation
+	b.Status = x.Status
+	b.Size = x.Size
+	b.Style = x.Style
 
 	return nil
 }
@@ -104,6 +167,9 @@ type ButtonConfig struct {
 	Payload      action.Payload `json:"payload"`
 	Confirmation *Confirmation  `json:"confirmation,omitempty"`
 	Modal        Component      `json:"modal,omitempty"`
+	Status       ButtonStatus   `json:"status,omitempty"`
+	Size         ButtonSize     `json:"size,omitempty"`
+	Style        ButtonStyle    `json:"style,omitempty"`
 }
 
 // MarshalJSON implements json.Marshaler
diff --git a/web/package-lock.json b/web/package-lock.json
index 2b9c46e4..b5c71490 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -4459,6 +4459,14 @@
       "integrity": "sha512-4Th98KlMHr5+JkxfcoDT//6vY8vM+iSPrLNpHhRyLx2CFYi8e2RfqPLdpbnpo0Q5lQC5hNB79yes07zb02fvCw==",
       "dev": true
     },
+    "@cds/angular": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/@cds/angular/-/angular-5.1.1.tgz",
+      "integrity": "sha512-sIVVUA74/RvyYwtfOmKh/Oyv0o7hGR/1dhr9nckMDersw0Iaiz363DWT+gdza4CehLydHLJfmfa/NJvWADrslg==",
+      "requires": {
+        "tslib": "^2.0.0"
+      }
+    },
     "@cds/city": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@cds/city/-/city-1.1.0.tgz",
@@ -8036,9 +8044,9 @@
       "dev": true
     },
     "@types/node": {
-      "version": "14.14.35",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.35.tgz",
-      "integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==",
+      "version": "14.14.41",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz",
+      "integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g==",
       "dev": true
     },
     "@types/node-fetch": {
diff --git a/web/package.json b/web/package.json
index 16abe1be..1ef2409b 100644
--- a/web/package.json
+++ b/web/package.json
@@ -54,6 +54,7 @@
     "@angular/platform-browser": "^11.2.8",
     "@angular/platform-browser-dynamic": "^11.2.8",
     "@angular/router": "^11.2.8",
+    "@cds/angular": "^5.1.1",
     "@cds/core": "^5.1.0",
     "@clr/angular": "^5.1.0",
     "@clr/icons": "^5.1.0",
diff --git a/web/src/app/modules/shared/components/presentation/button-group/button-group.component.html b/web/src/app/modules/shared/components/presentation/button-group/button-group.component.html
index 1e93977e..376598a2 100644
--- a/web/src/app/modules/shared/components/presentation/button-group/button-group.component.html
+++ b/web/src/app/modules/shared/components/presentation/button-group/button-group.component.html
@@ -6,18 +6,3 @@
   </app-button>
 </div>
 
-<app-view-modal *ngIf="modalView" [view]="modalView"></app-view-modal>
-<clr-modal [(clrModalOpen)]="isModalOpen">
-  <h3 class="modal-title">{{ modalTitle }}</h3>
-  <div class="modal-body">
-    <div [innerHTML]="modalBody" ngPreserveWhitespaces></div>
-  </div>
-  <div class="modal-footer">
-    <button type="button" class="btn btn-outline" (click)="cancelModal()">
-      Cancel
-    </button>
-    <button type="button" class="btn btn-primary" (click)="acceptModal()">
-      OK
-    </button>
-  </div>
-</clr-modal>
diff --git a/web/src/app/modules/shared/components/presentation/button-group/button-group.component.ts b/web/src/app/modules/shared/components/presentation/button-group/button-group.component.ts
index d60b9374..940cf0da 100644
--- a/web/src/app/modules/shared/components/presentation/button-group/button-group.component.ts
+++ b/web/src/app/modules/shared/components/presentation/button-group/button-group.component.ts
@@ -1,20 +1,6 @@
-import {
-  Component,
-  EventEmitter,
-  Output,
-  SecurityContext,
-} from '@angular/core';
-import {
-  ButtonGroupView,
-  Confirmation,
-  View,
-  ModalView,
-} from '../../../models/content';
-import { ActionService } from '../../../services/action/action.service';
+import { Component, EventEmitter, Output } from '@angular/core';
+import { ButtonGroupView } from '../../../models/content';
 import { AbstractViewComponent } from '../../abstract-view/abstract-view.component';
-import { ModalService } from '../../../services/modal/modal.service';
-import { DomSanitizer } from '@angular/platform-browser';
-import { parse } from 'marked';
 
 @Component({
   selector: 'app-button-group',
@@ -24,84 +10,13 @@ import { parse } from 'marked';
 export class ButtonGroupComponent extends AbstractViewComponent<ButtonGroupView> {
   @Output() buttonLoad: EventEmitter<boolean> = new EventEmitter(true);
 
-  isModalOpen = false;
-  modalTitle = '';
-  modalBody = '';
-  payload = {};
-  class = '';
-
-  modalView: View;
-
-  constructor(
-    private actionService: ActionService,
-    private modalService: ModalService,
-    private sanitize: DomSanitizer
-  ) {
+  constructor() {
     super();
   }
 
-  update() {
-    if (this.v.config.buttons) {
-      this.v.config.buttons.forEach(button => {
-        if (button.config.confirmation) {
-          this.class = 'btn-danger-outline btn-sm';
-        } else {
-          this.class = 'btn-outline btn-sm';
-        }
-        if (button.config.modal) {
-          this.modalView = button.config.modal;
-          const modal = this.modalView as ModalView;
-          this.modalService.setState(modal.config.opened);
-        }
-      });
-    }
-  }
-
-  onClick(payload: {}, confirmation?: Confirmation, modal?: View) {
-    if (modal) {
-      this.modalService.openModal();
-    }
-    if (confirmation) {
-      this.activateModal(payload, confirmation);
-    } else {
-      this.buttonLoad.emit(true);
-      this.doAction(payload);
-    }
-  }
-
-  cancelModal() {
-    this.resetModal();
-  }
-
-  acceptModal() {
-    const payload = this.payload;
-    this.resetModal();
-    this.doAction(payload);
-  }
+  update() {}
 
-  trackByFn(index, item) {
+  trackByFn(index) {
     return index;
   }
-
-  private doAction(payload: {}) {
-    this.actionService.perform(payload);
-  }
-
-  private activateModal(payload: {}, confirmation: Confirmation) {
-    this.modalTitle = confirmation.title;
-    this.modalBody = this.sanitize.sanitize(
-      SecurityContext.HTML,
-      parse(confirmation.body)
-    );
-    this.isModalOpen = true;
-
-    this.payload = payload;
-  }
-
-  private resetModal() {
-    this.isModalOpen = false;
-    this.modalBody = '';
-    this.modalTitle = '';
-    this.payload = {};
-  }
 }
diff --git a/web/src/app/modules/shared/components/presentation/button/button.component.html b/web/src/app/modules/shared/components/presentation/button/button.component.html
index efccd0b2..301b7d28 100644
--- a/web/src/app/modules/shared/components/presentation/button/button.component.html
+++ b/web/src/app/modules/shared/components/presentation/button/button.component.html
@@ -1,8 +1,12 @@
-<button
-  class="{{ class }}"
+<cds-button
+  action="{{ style }}"
+  status="{{ status }}"
+  size="{{ size }}"
+  disabled="{{ disabled }}"
+  block="{{ block }}"
   (click)="onClick(v.config.payload, v.config.confirmation, v.config.modal)">
   {{ v.config.name }}
-</button>
+</cds-button>
 
 <app-view-modal *ngIf="modalView" [view]="modalView"></app-view-modal>
 <clr-modal [(clrModalOpen)]="isModalOpen">
diff --git a/web/src/app/modules/shared/components/presentation/button/button.component.ts b/web/src/app/modules/shared/components/presentation/button/button.component.ts
index 06a11ed9..29e51105 100644
--- a/web/src/app/modules/shared/components/presentation/button/button.component.ts
+++ b/web/src/app/modules/shared/components/presentation/button/button.component.ts
@@ -11,6 +11,7 @@ import {
   ModalView,
 } from '../../../models/content';
 
+import '@cds/core/button/register';
 import { AbstractViewComponent } from '../../abstract-view/abstract-view.component';
 import { parse } from 'marked';
 import { DomSanitizer } from '@angular/platform-browser';
@@ -29,7 +30,11 @@ export class ButtonComponent extends AbstractViewComponent<ButtonView> {
   modalTitle = '';
   modalBody = '';
   payload = {};
-  class = '';
+  style = 'outline';
+  status = '';
+  size = 'sm';
+  disabled = null;
+  block = null;
 
   modalView: View;
 
@@ -42,16 +47,33 @@ export class ButtonComponent extends AbstractViewComponent<ButtonView> {
   }
 
   update() {
-    if (this.v.config.confirmation) {
-      this.class = 'btn btn-danger-outline btn-sm';
-    } else {
-      this.class = 'btn btn-outline btn-sm';
-    }
-    if (this.v.config.modal) {
+    let button = this.v.config;
+    if (button.modal) {
       this.modalView = this.v.config.modal;
       const modal = this.modalView as ModalView;
       this.modalService.setState(modal.config.opened);
     }
+    if (button.confirmation) {
+      this.status = 'danger';
+    } else {
+      if (button.style) {
+        this.style = button.style;
+      }
+      if (button.status) {
+        if (button.status === 'disabled') {
+          this.disabled = true;
+        } else {
+          this.status = button.status;
+        }
+      }
+      if (button.size) {
+        if (button.size === 'block') {
+          this.block = true;
+        } else {
+          this.size = button.size;
+        }
+      }
+    }
   }
 
   onClick(payload: {}, confirmation?: Confirmation, modal?: View) {
diff --git a/web/src/app/modules/shared/models/content.ts b/web/src/app/modules/shared/models/content.ts
index 584c49e9..58669104 100644
--- a/web/src/app/modules/shared/models/content.ts
+++ b/web/src/app/modules/shared/models/content.ts
@@ -611,5 +611,8 @@ export interface ButtonView extends View {
     name: string;
     confirmation?: Confirmation;
     modal?: ModalView;
+    status?: string;
+    size?: string;
+    style?: string;
   };
 }
diff --git a/web/src/app/modules/shared/shared.module.ts b/web/src/app/modules/shared/shared.module.ts
index 51ae2f8a..bf1836c0 100644
--- a/web/src/app/modules/shared/shared.module.ts
+++ b/web/src/app/modules/shared/shared.module.ts
@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { TextComponent } from './components/presentation/text/text.component';
 import { ClarityModule } from '@clr/angular';
+import { CdsModule } from '@cds/angular';
 import { TitleComponent } from './components/presentation/title/title.component';
 import { AccordionComponent } from './components/presentation/accordion/accordion.component';
 import { AlertComponent } from './components/presentation/alert/alert.component';
@@ -247,6 +248,7 @@ import { ButtonComponent } from './components/presentation/button/button.compone
     ReactiveFormsModule,
     ResizableModule,
     RouterModule,
+    CdsModule,
   ],
   providers: [highlightProvider(), dynamicComponents()],
   exports: [
diff --git a/web/src/stories/button.group.stories.mdx b/web/src/stories/button.group.stories.mdx
index b5dd37c3..fae81583 100644
--- a/web/src/stories/button.group.stories.mdx
+++ b/web/src/stories/button.group.stories.mdx
@@ -25,28 +25,33 @@ Buttons requiring confirmation are red.</p>
 
 <Canvas withToolbar>
   <Story name="ButtonGroup component" parameters={{ docs: buttonDocs }} height="150px" >
-  {{
+    {{
       props: {
         view:  object('View', {
           config: {
             buttons: [
               {
-                name: 'Delete',
-                payload: {},
-                confirmation: {
-                  title: 'Delete Pod',
-                  body: 'Are you sure you want to delete *Pod* **pod**?',
+                metadata: {
+                  type: "button"
                 },
+                config: {
+                  name: 'Delete',
+                  payload: {},
+                  confirmation: {
+                    title: 'Delete Pod',
+                    body: 'Are you sure you want to delete *Pod* **pod**?',
+                  },
+                }
               },
             ],
           },
           metadata: {
             type: 'buttonGroup',
           },
-      }),
+        }),
       },
       component: ButtonGroupComponent,
-      }}
+    }}
   </Story>
 </Canvas>
 
diff --git a/web/src/stories/button.stories.mdx b/web/src/stories/button.stories.mdx
index de06c4be..07e75894 100644
--- a/web/src/stories/button.stories.mdx
+++ b/web/src/stories/button.stories.mdx
@@ -2,14 +2,73 @@ import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks';
 import { object, withKnobs } from '@storybook/addon-knobs';
 import { ButtonComponent } from "../app/modules/shared/components/presentation/button/button.component";
 
-export const buttonDocs= { source: { code: `button := component.NewButton()
-component.NewButton("button",
-      action.CreatePayload(octant.ActionDeleteObject, key.ToActionPayload()),
-      component.WithButtonConfirmation(
-        "Delete Pod",
-        "Are you sure you want to delete *Pod* **pod**?",
-      ))
-`}}
+export const buttonDocs= [
+  { source: { code: `
+        component.NewButton("button",
+              action.CreatePayload(octant.ActionDeleteObject, key.ToActionPayload()),
+              component.WithButtonConfirmation(
+                "Delete Pod",
+                "Are you sure you want to delete *Pod* **pod**?",
+              ))`
+  }}
+]
+
+export const buttonStyleDocs= [
+  { source: { code: `
+      component.NewButton("Button Style Outline",
+        action.Payload{},
+        component.WithButtonStyle(component.ButtonStyleOutline))`
+  }},
+  { source: { code: `
+      component.NewButton("Button Style Solid",
+        action.Payload{},
+        component.WithButtonStyle(component.ButtonStyleSolid))`
+  }},
+  { source: { code: `
+      component.NewButton("Button Style Flat",
+        action.Payload{},
+        component.WithButtonStyle(component.ButtonStyleFlat))`
+  }}
+]
+
+export const buttonStatusDocs= [
+  { source: { code: `
+      component.NewButton("Button Status Success",
+        action.Payload{},
+        component.WithButtonStatus(component.ButtonStatusSuccess))`
+  }},
+  { source: { code: `
+      component.NewButton("Button Status Info",
+        action.Payload{},
+        component.WithButtonStatus(component.ButtonStatusInfo))`
+  }},
+  { source: { code: `
+      component.NewButton("Button Status Danger",
+        action.Payload{},
+        component.WithButtonStatus(component.ButtonStatusDanger))`
+  }},
+  { source: { code: `
+      component.NewButton("Button Status Disabled",
+        action.Payload{},
+        component.WithButtonStatus(component.ButtonStatusDisabled))`
+  }}
+]
+
+export const buttonSizeDocs= [
+  { source: { code: `
+      component.NewButton("Button Size Block",
+        action.Payload{},
+        component.WithButtonStatus(component.ButtonSizeBlock))`
+  }},
+  { source: { code: `
+      component.NewButton("Button Size Large",
+        action.Payload{},
+        component.WithButtonStatus(component.ButtonSizeLarge))`
+  }},
+]
+
+
+
 
 
 <h1>Button component</h1>
@@ -23,25 +82,200 @@ component.NewButton("button",
 <Meta title="Components/Button" component={ButtonComponent} />
 
 <Canvas withToolbar>
-  <Story name="Button component" parameters={{ docs: buttonDocs }} height="150px" >
-  {{
+  <Story name="Button component" parameters={{ docs: buttonDocs[0] }} height="150px">
+    {{
+        props: {
+          view:  object('View', {
+            config: {
+              name: 'Delete',
+              payload: {},
+              confirmation: {
+                title: 'Delete Pod',
+                body: 'Are you sure you want to delete *Pod* **pod**?',
+              },
+            },
+            metadata: {
+              type: 'button',
+            },
+          }),
+        },
+        component: ButtonComponent,
+    }}
+  </Story>
+</Canvas>
+
+<h2>Buttons with size</h2>
+<p>Current available sizes are: <strong>ButtonSizeBlock</strong> and <strong>ButtonSizeLarge</strong>.</p>
+<h2>Example</h2>
+
+<Canvas withToolbar>
+  <Story name="Button size block" parameters={{ docs: buttonSizeDocs[0] }} height="150px">
+    {{
       props: {
         view:  object('View', {
           config: {
-            name: 'Delete',
+            name: 'Button Size Block',
             payload: {},
-            confirmation: {
-              title: 'Delete Pod',
-              body: 'Are you sure you want to delete *Pod* **pod**?',
-            },
+            size: 'block'
+          },
+          metadata: {
+            type: 'button',
+          },
+        }),
+      },
+      component: ButtonComponent,
+    }}
+  </Story>
+  <Story name="Button size large" parameters={{ docs: buttonSizeDocs[1] }} height="150px">
+    {{
+      props: {
+        view:  object('View', {
+          config: {
+            name: 'Button Size Large',
+            payload: {},
+            size: 'lg'
+          },
+          metadata: {
+            type: 'button',
+          },
+        }),
+      },
+      component: ButtonComponent,
+    }}
+  </Story>
+</Canvas>
+
+
+<h2>Buttons with status</h2>
+<p>Current available status are: <strong>ButtonStatusSuccess</strong>, <strong>ButtonStatusInfo</strong>, <strong>ButtonStatusDanger</strong> and <strong>ButtonStatusDisabled</strong>.</p>
+<h2>Example</h2>
+
+<Canvas withToolbar>
+  <Story name="Button status success" parameters={{ docs: buttonStatusDocs[0] }} height="150px">
+    {{
+      props: {
+        view:  object('View', {
+          config: {
+            name: 'Button Status Success',
+            payload: {},
+            status: 'success'
+          },
+          metadata: {
+            type: 'button',
+          },
+        }),
+      },
+      component: ButtonComponent,
+    }}
+  </Story>
+  <Story name="Button status info" parameters={{ docs: buttonStatusDocs[1] }} height="150px">
+    {{
+      props: {
+        view:  object('View', {
+          config: {
+            name: 'Button Status Info',
+            payload: {},
+            status: 'info'
+          },
+          metadata: {
+            type: 'button',
+          },
+        }),
+      },
+      component: ButtonComponent,
+    }}
+  </Story>
+  <Story name="Button status danger" parameters={{ docs: buttonStatusDocs[2] }} height="150px">
+    {{
+      props: {
+        view:  object('View', {
+          config: {
+            name: 'Button Status Danger',
+            payload: {},
+            status: 'danger'
+          },
+          metadata: {
+            type: 'button',
+          },
+        }),
+      },
+      component: ButtonComponent,
+    }}
+  </Story>
+  <Story name="Button status disabled" parameters={{ docs: buttonStatusDocs[3] }} height="150px">
+    {{
+      props: {
+        view:  object('View', {
+          config: {
+            name: 'Button Status Disabled',
+            payload: {},
+            status: 'disabled'
+          },
+          metadata: {
+            type: 'button',
+          },
+        }),
+      },
+      component: ButtonComponent,
+    }}
+  </Story>
+</Canvas>
+
+<h2>Buttons with style</h2>
+<p>Current available styles are: <strong>ButtonStyleOutline</strong>, <strong>ButtonStyleSolid</strong> and <strong>ButtonStyleFlat</strong>.</p>
+<h2>Example</h2>
+
+<Canvas withToolbar>
+  <Story name="Button style outline" parameters={{ docs: buttonStyleDocs[0] }} height="150px">
+    {{
+      props: {
+        view:  object('View', {
+          config: {
+            name: 'Button Style Outline',
+            payload: {},
+            style: 'outline'
+          },
+          metadata: {
+            type: 'button',
+          },
+        }),
+      },
+      component: ButtonComponent,
+    }}
+  </Story>
+  <Story name="Button style solid" parameters={{ docs: buttonStyleDocs[1] }} height="150px">
+    {{
+      props: {
+        view:  object('View', {
+          config: {
+            name: 'Button Style Solid',
+            payload: {},
+            style: 'primary'
+          },
+          metadata: {
+            type: 'button',
+          },
+        }),
+      },
+      component: ButtonComponent,
+    }}
+  </Story>
+  <Story name="Button style flat" parameters={{ docs: buttonStyleDocs[2] }} height="150px">
+    {{
+      props: {
+        view:  object('View', {
+          config: {
+            name: 'Button Style Flat',
+            payload: {},
+            style: 'link'
           },
           metadata: {
             type: 'button',
           },
-      }),
+        }),
       },
       component: ButtonComponent,
-      }}
+    }}
   </Story>
 </Canvas>
 


Test patch:
diff --git a/pkg/view/component/button_test.go b/pkg/view/component/button_test.go
index d530c64d..6e87708f 100644
--- a/pkg/view/component/button_test.go
+++ b/pkg/view/component/button_test.go
@@ -46,3 +46,86 @@ func Test_Button_Marshal(t *testing.T) {
 		})
 	}
 }
+
+func Test_Button_Options(t *testing.T) {
+	cases := []struct {
+		name     string
+		optsFunc []ButtonOption
+		expected *Button
+	}{
+		{
+			name: "modal",
+			optsFunc: []ButtonOption{
+				WithModal(&Modal{
+					Base:   newBase(TypeModal, TitleFromString("modal title")),
+					Config: ModalConfig{},
+				}),
+			},
+			expected: &Button{
+				Base: newBase(TypeButton, nil),
+				Config: ButtonConfig{
+					Modal: NewModal(TitleFromString("modal title")),
+				},
+			},
+		},
+		{
+			name: "status",
+			optsFunc: []ButtonOption{
+				WithButtonStatus(ButtonStatusDanger),
+			},
+			expected: &Button{
+				Base: newBase(TypeButton, nil),
+				Config: ButtonConfig{
+					Status: ButtonStatusDanger,
+				},
+			},
+		},
+		{
+			name: "size",
+			optsFunc: []ButtonOption{
+				WithButtonSize(ButtonSizeMedium),
+			},
+			expected: &Button{
+				Base: newBase(TypeButton, nil),
+				Config: ButtonConfig{
+					Size: ButtonSizeMedium,
+				},
+			},
+		},
+		{
+			name: "style",
+			optsFunc: []ButtonOption{
+				WithButtonStyle(ButtonStyleOutline),
+			},
+			expected: &Button{
+				Base: newBase(TypeButton, nil),
+				Config: ButtonConfig{
+					Style: ButtonStyleOutline,
+				},
+			},
+		},
+		{
+			name: "multiple opts",
+			optsFunc: []ButtonOption{
+				WithButtonStatus(ButtonStatusSuccess),
+				WithButtonSize(ButtonSizeBlock),
+				WithButtonStyle(ButtonStyleFlat),
+			},
+			expected: &Button{
+				Base: newBase(TypeButton, nil),
+				Config: ButtonConfig{
+					Status: ButtonStatusSuccess,
+					Size:   ButtonSizeBlock,
+					Style:  ButtonStyleFlat,
+				},
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := NewButton("", nil, tc.optsFunc...)
+			require.Equal(t, tc.expected, got)
+		})
+	}
+}