Repository: speee/jsx-slack (ts)

Issue:
Confirm Button should be green
```
<Confirm title="Commit your action" confirm="Yes, please" deny="Cancel">
  <b>Are you sure?</b> Please confirm your action again.
</Confirm>
```
^ Inside a RadioButtonGroup confirm leads to:

![image](https://user-images.githubusercontent.com/13177748/74614280-ec12b200-5116-11ea-85f7-88c85cf6b83c.png)

I propose the button should be green by default or adjustable.

Pull request:
Update <Confirm> component to match into the latest spec
Updated <Confirm> component to match into the latest spec of Slack API.

- All props of `<Confirm>` component have made optional.
  - Use localized default label if omitted `deny` and `confirm`.
  - `title` also can omit but it makes an extra space for title in the dialog when using desktop client.
* [Added `style` prop to define the style of the primary button.](https://api.slack.com/reference/block-kit/composition-objects#confirm)
  * Slack renders the button as `primary` (green or blue text) if not defined.
  * `<Button>` will inherit `style` prop into assigned `<Confirm>` if not defined `style` in `<Confirm>`.

Resolves #114 and #138.

### ToDo

- [x] Update documentation

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5107aaf..ad062d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,8 +2,14 @@
 
 ## [Unreleased]
 
+### Added
+
+- `style` prop for `<Confirm>` composition object component ([#114](https://github.com/speee/jsx-slack/issues/114), [#139](https://github.com/speee/jsx-slack/pull/139))
+- `<Button>` inherits its style to assigned confirm composition object if `<Confirm>` has not defined style ([#139](https://github.com/speee/jsx-slack/pull/139))
+
 ### Changed
 
+- All props of `<Confirm>` component have made optional ([#138](https://github.com/speee/jsx-slack/issues/138), [#139](https://github.com/speee/jsx-slack/pull/139))
 - Upgrade dependent packages to the latest version ([#137](https://github.com/speee/jsx-slack/pull/137))
 
 ## v1.6.0 - 2020-03-20
diff --git a/demo/schema.js b/demo/schema.js
index 2201674..56be367 100644
--- a/demo/schema.js
+++ b/demo/schema.js
@@ -340,7 +340,12 @@ const schema = {
 
   // Composition objects
   Confirm: {
-    attrs: { title: null, confirm: null, deny: null },
+    attrs: {
+      title: null,
+      confirm: null,
+      deny: null,
+      style: ['primary', 'danger'],
+    },
     children: ['Mrkdwn', ...markupHTML],
   },
   Mrkdwn: {
diff --git a/docs/block-elements.md b/docs/block-elements.md
index dd3c748..cb3273e 100644
--- a/docs/block-elements.md
+++ b/docs/block-elements.md
@@ -28,8 +28,8 @@ A simple button to send action to registered Slack App, or open external URL. `<
 - `name` / `actionId` (optional): An identifier for the action.
 - `value` (optional): A string value to send to Slack App when clicked button.
 - `url` (optional): URL to load when clicked button.
-- `style` (optional): Select the colored button decoration from `primary` and `danger`.
-- `confirm` (optional): [`<Confirm>` element](#confirm) to show confirmation dialog.
+- `style` (optional): Select the color scheme of the button from `primary` and `danger`.
+- `confirm` (optional): [`<Confirm>` element](#confirm) to show confirmation dialog. If `<Confirm>` has not defined `style` prop, the style for confirm button may be inherited from the assigned button.
 
 ### <a name="select" id="select"></a> [`<Select>`: Select menu with static options](https://api.slack.com/reference/messaging/block-elements#static_select)
 
@@ -674,9 +674,12 @@ You can use [HTML-like formatting](./html-like-formatting.md) to the content of
 
 #### Props
 
-- `title` (**required**): The title of confirmation dialog.
-- `confirm` (**required**): A text content of the button to confirm.
-- `deny` (**required**): A text content of the button to cancel.
+- `title` (optional): The title of confirmation dialog.
+- `confirm` (optional): A text content of the button to confirm. Slack would use the default localized label if not defined.
+- `deny` (optional): A text content of the button to cancel. Slack would use the default localized label if not defined.
+- `style` (optional): Select the color scheme of the confirm button. _When not defined, jsx-slack may inherit a value from assigned component such as [`<Button>`](#button)._
+  - `primary`: Green button on desktop, and blue text on mobile. (Slack's default if not defined)
+  - `danger`: Red button on desktop, and red text on mobile.
 
 ### <a name="mrkdwn" id="mrkdwn"></a> [`<Mrkdwn>`: Text composition object for `mrkdwn` type](https://api.slack.com/reference/block-kit/composition-objects#text)
 
diff --git a/src/block-kit/composition/Confirm.tsx b/src/block-kit/composition/Confirm.tsx
index 497367e..1cddd91 100644
--- a/src/block-kit/composition/Confirm.tsx
+++ b/src/block-kit/composition/Confirm.tsx
@@ -4,18 +4,24 @@ import { JSXSlack } from '../../jsx'
 import { ObjectOutput } from '../../utils'
 import { plainText, mrkdwnFromNode } from './utils'
 
+export interface ConfirmComposition extends SlackConfirm {
+  style?: 'danger' | 'primary'
+}
+
 export interface ConfirmProps {
   children: JSXSlack.Children<{}>
-  confirm: string
-  deny: string
-  title: string
+  confirm?: string
+  deny?: string
+  style?: 'danger' | 'primary'
+  title?: string
 }
 
 export const Confirm: JSXSlack.FC<ConfirmProps> = (props) => (
-  <ObjectOutput<SlackConfirm>
-    title={plainText(props.title)}
+  <ObjectOutput<ConfirmComposition>
+    title={props.title !== undefined ? plainText(props.title) : undefined}
     text={mrkdwnFromNode(props.children)}
-    confirm={plainText(props.confirm)}
-    deny={plainText(props.deny)}
+    confirm={props.confirm !== undefined ? plainText(props.confirm) : undefined}
+    deny={props.deny !== undefined ? plainText(props.deny) : undefined}
+    style={props.style}
   />
 )
diff --git a/src/block-kit/elements/Button.tsx b/src/block-kit/elements/Button.tsx
index 489162d..94259a4 100644
--- a/src/block-kit/elements/Button.tsx
+++ b/src/block-kit/elements/Button.tsx
@@ -2,7 +2,7 @@
 import { Button as SlackButton } from '@slack/types'
 import { JSXSlack } from '../../jsx'
 import { ObjectOutput, PlainText } from '../../utils'
-import { ConfirmProps } from '../composition/Confirm'
+import { ConfirmComposition, ConfirmProps } from '../composition/Confirm'
 import { plainText } from '../composition/utils'
 
 export interface ButtonProps {
@@ -10,19 +10,30 @@ export interface ButtonProps {
   children: JSXSlack.Children<{}>
   confirm?: JSXSlack.Node<ConfirmProps>
   name?: string
-  style?: SlackButton['style']
+  style?: 'danger' | 'primary'
   url?: string
   value?: string
 }
 
-export const Button: JSXSlack.FC<ButtonProps> = (props) => (
-  <ObjectOutput<SlackButton>
-    type="button"
-    text={plainText(JSXSlack(<PlainText>{props.children}</PlainText>))}
-    action_id={props.actionId || props.name}
-    confirm={props.confirm ? JSXSlack(props.confirm) : undefined}
-    style={props.style}
-    url={props.url}
-    value={props.value}
-  />
-)
+export const Button: JSXSlack.FC<ButtonProps> = (props) => {
+  let confirm: ConfirmComposition | undefined
+
+  if (props.confirm) {
+    confirm = JSXSlack(props.confirm) as ConfirmComposition
+
+    if (confirm.style === undefined && props.style !== undefined)
+      confirm.style = props.style
+  }
+
+  return (
+    <ObjectOutput<SlackButton>
+      type="button"
+      text={plainText(JSXSlack(<PlainText>{props.children}</PlainText>))}
+      action_id={props.actionId || props.name}
+      confirm={confirm}
+      style={props.style}
+      url={props.url}
+      value={props.value}
+    />
+  )
+}


Test patch:
diff --git a/test/block-kit/block-elements/composition-objects.tsx b/test/block-kit/block-elements/composition-objects.tsx
index daf7444..7637231 100644
--- a/test/block-kit/block-elements/composition-objects.tsx
+++ b/test/block-kit/block-elements/composition-objects.tsx
@@ -22,11 +22,7 @@ describe('Composition objects', () => {
             <Actions blockId="actions">
               <Button
                 confirm={
-                  <Confirm
-                    title="Share to SNS"
-                    confirm="Yes, please"
-                    deny="Cancel"
-                  >
+                  <Confirm>
                     <b>Are you sure?</b> Message will be share.
                   </Confirm>
                 }
@@ -43,26 +39,11 @@ describe('Composition objects', () => {
             "elements": Array [
               Object {
                 "confirm": Object {
-                  "confirm": Object {
-                    "emoji": true,
-                    "text": "Yes, please",
-                    "type": "plain_text",
-                  },
-                  "deny": Object {
-                    "emoji": true,
-                    "text": "Cancel",
-                    "type": "plain_text",
-                  },
                   "text": Object {
                     "text": "*Are you sure?* Message will be share.",
                     "type": "mrkdwn",
                     "verbatim": true,
                   },
-                  "title": Object {
-                    "emoji": true,
-                    "text": "Share to SNS",
-                    "type": "plain_text",
-                  },
                 },
                 "text": Object {
                   "emoji": true,
@@ -88,6 +69,7 @@ describe('Composition objects', () => {
                     title="Share to SNS"
                     confirm="Yes, please"
                     deny="Cancel"
+                    style="primary"
                   >
                     <Mrkdwn verbatim={false}>
                       <b>Are you sure?</b> Message will be share.
@@ -117,6 +99,7 @@ describe('Composition objects', () => {
                     "text": "Cancel",
                     "type": "plain_text",
                   },
+                  "style": "primary",
                   "text": Object {
                     "text": "*Are you sure?* Message will be share.",
                     "type": "mrkdwn",
diff --git a/test/block-kit/block-elements/interactive-components.tsx b/test/block-kit/block-elements/interactive-components.tsx
index e072d2c..e1eb4cf 100644
--- a/test/block-kit/block-elements/interactive-components.tsx
+++ b/test/block-kit/block-elements/interactive-components.tsx
@@ -131,6 +131,80 @@ describe('Interactive components', () => {
         )
       ).toStrictEqual([buttonAction])
     })
+
+    it('inherits style prop into the confirm composition object', () => {
+      const confirm = <Confirm>Are you sure?</Confirm>
+
+      expect(
+        JSXSlack(
+          <Blocks>
+            <Actions blockId="actions">
+              <Button style="primary" confirm={confirm}>
+                Button
+              </Button>
+            </Actions>
+          </Blocks>
+        )
+      ).toStrictEqual([
+        action({
+          type: 'button',
+          text: { type: 'plain_text', text: 'Button', emoji: true },
+          style: 'primary',
+          confirm: {
+            text: { type: 'mrkdwn', text: 'Are you sure?', verbatim: true },
+            style: 'primary',
+          },
+        } as any),
+      ])
+
+      expect(
+        JSXSlack(
+          <Blocks>
+            <Actions blockId="actions">
+              <Button style="danger" confirm={confirm}>
+                Button
+              </Button>
+            </Actions>
+          </Blocks>
+        )
+      ).toStrictEqual([
+        action({
+          type: 'button',
+          text: { type: 'plain_text', text: 'Button', emoji: true },
+          style: 'danger',
+          confirm: {
+            text: { type: 'mrkdwn', text: 'Are you sure?', verbatim: true },
+            style: 'danger',
+          },
+        } as any),
+      ])
+
+      // Prefer style defined in composition object to button
+      expect(
+        JSXSlack(
+          <Blocks>
+            <Actions blockId="actions">
+              <Button
+                style="danger"
+                confirm={<Confirm style="primary">Are you sure?</Confirm>}
+              >
+                Button
+              </Button>
+            </Actions>
+          </Blocks>
+        )
+      ).toStrictEqual([
+        action({
+          type: 'button',
+          text: { type: 'plain_text', text: 'Button', emoji: true },
+          style: 'danger',
+          confirm: {
+            text: { type: 'mrkdwn', text: 'Are you sure?', verbatim: true },
+            style: 'primary',
+          },
+        } as any),
+      ])
+    })
   })
 
   describe('<Select>', () => {