From 31ceda5bc90f76a7aaf70d23a76f637493c7f05e Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Fri, 7 Feb 2020 17:18:43 +0900 Subject: [PATCH 01/11] Stop caching node_modules in CI --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3f2bf7b3..a2e8b251 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,7 +29,6 @@ commands: - save_cache: key: dependencies-{{ .Environment.CI_CACHE_KEY }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "yarn.lock" }}-{{ .Branch }} paths: - - node_modules - ~/.cache/yarn - run: From 3971a6038290ed3f88982731af82203a39eba9c7 Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Fri, 7 Feb 2020 19:52:27 +0900 Subject: [PATCH 02/11] Add and component --- src/block-kit/Actions.tsx | 11 +-- src/block-kit/Blocks.tsx | 10 +-- src/block-kit/Input.tsx | 1 + src/block-kit/Section.tsx | 19 +++--- src/block-kit/elements/Checkbox.tsx | 95 ++++++++++++++++++++++++++ src/block-kit/elements/RadioButton.tsx | 4 +- src/block-kit/index.ts | 9 +-- 7 files changed, 124 insertions(+), 25 deletions(-) create mode 100644 src/block-kit/elements/Checkbox.tsx diff --git a/src/block-kit/Actions.tsx b/src/block-kit/Actions.tsx index fc42e0a2..5adefa21 100644 --- a/src/block-kit/Actions.tsx +++ b/src/block-kit/Actions.tsx @@ -16,14 +16,15 @@ interface ActionsProps extends BlockComponentProps { export const actionTypes = [ 'button', - 'static_select', - 'external_select', - 'users_select', - 'conversations_select', 'channels_select', - 'overflow', + 'checkboxes', + 'conversations_select', 'datepicker', + 'external_select', + 'overflow', 'radio_buttons', + 'static_select', + 'users_select', ] as const export const Actions: JSXSlack.FC = props => { diff --git a/src/block-kit/Blocks.tsx b/src/block-kit/Blocks.tsx index 81787b0e..e5887dd9 100644 --- a/src/block-kit/Blocks.tsx +++ b/src/block-kit/Blocks.tsx @@ -31,14 +31,14 @@ const knownSectionAccessories: KnownMap = new Map() const basicBlocks = ['actions', 'context', 'divider', 'image', 'section'] // Blocks +const actionTypeFilterForMessaging = (t: string) => + !['checkboxes', 'radio_buttons'].includes(t) + knownBlocks.set(undefined, [...basicBlocks, 'file']) -knownActions.set( - undefined, - actionTypes.filter(t => t !== 'radio_buttons') -) +knownActions.set(undefined, actionTypes.filter(actionTypeFilterForMessaging)) knownSectionAccessories.set( undefined, - sectionAccessoryTypes.filter(t => t !== 'radio_buttons') + sectionAccessoryTypes.filter(actionTypeFilterForMessaging) ) // Modal diff --git a/src/block-kit/Input.tsx b/src/block-kit/Input.tsx index 1664061a..e9d9c8d4 100644 --- a/src/block-kit/Input.tsx +++ b/src/block-kit/Input.tsx @@ -70,6 +70,7 @@ export interface InternalSubmitObject { const knownInputs = [ 'channels_select', + 'checkboxes', 'conversations_select', 'datepicker', 'external_select', diff --git a/src/block-kit/Section.tsx b/src/block-kit/Section.tsx index 9acaf142..3c7bca51 100644 --- a/src/block-kit/Section.tsx +++ b/src/block-kit/Section.tsx @@ -19,21 +19,22 @@ interface FieldInternalObject { } export const sectionAccessoryTypes = [ - 'image', 'button', - 'static_select', - 'external_select', - 'users_select', - 'conversations_select', 'channels_select', - 'multi_static_select', + 'checkboxes', + 'conversations_select', + 'datepicker', + 'external_select', + 'image', + 'multi_channels_select', + 'multi_conversations_select', 'multi_external_select', + 'multi_static_select', 'multi_users_select', - 'multi_conversations_select', - 'multi_channels_select', 'overflow', - 'datepicker', 'radio_buttons', + 'static_select', + 'users_select', ] as const export const Section: JSXSlack.FC + confirm?: JSXSlack.Node + name?: string + values?: string[] +} + +export type CheckboxGroupProps = WithInputProps + +export interface CheckboxProps { + checked?: boolean + children: JSXSlack.Children<{}> + description?: JSXSlack.Children<{}> + value: string +} + +interface CheckboxInternal extends CheckboxProps { + type: typeof checkboxInternal +} + +const filterCheckbox = children => + JSXSlack.normalizeChildren(children).filter( + c => + isNode(c) && + c.type === JSXSlack.NodeType.object && + c.props.type === checkboxInternal + ) as JSXSlack.Node[] + +const toOptionObject = (props: CheckboxInternal): CheckboxOption => { + const option: CheckboxOption = { + text: mrkdwnFromNode(props.children), + value: props.value, + } + + if (props.description) option.description = mrkdwnFromNode(props.description) + + return option +} + +export const CheckboxGroup: JSXSlack.FC = props => { + const states = new Map() + const values = props.values || [] + + const options = filterCheckbox(props.children).map(({ props: cProps }) => { + if (cProps.value) { + if (cProps.checked !== undefined) { + states.set(cProps.value, !!cProps.checked) + } else if (values.includes(cProps.value)) { + states.set(cProps.value, true) + } + } + return toOptionObject(cProps) + }) + + if (options.length === 0) + throw new Error(' must include least of one .') + + const initialOptions = options.reduce( + (arr, opt) => (opt.value && states.get(opt.value) ? [...arr, opt] : arr), + [] as CheckboxOption[] + ) + + const element = ( + 0 ? initialOptions : undefined} + confirm={props.confirm ? JSXSlack(props.confirm) : undefined} + /> + ) + + return props.label ? wrapInInput(element, props) : element +} + +export const Checkbox: JSXSlack.FC = props => ( + {...props} type={checkboxInternal} /> +) diff --git a/src/block-kit/elements/RadioButton.tsx b/src/block-kit/elements/RadioButton.tsx index fb1b4348..95dc5441 100644 --- a/src/block-kit/elements/RadioButton.tsx +++ b/src/block-kit/elements/RadioButton.tsx @@ -1,5 +1,5 @@ /** @jsx JSXSlack.h */ -import { Option } from '@slack/types' +import { Option, RadioButtons } from '@slack/types' import { ConfirmProps } from '../composition/Confirm' import { plainText } from '../composition/utils' import { JSXSlack } from '../../jsx' @@ -63,7 +63,7 @@ export const RadioButtonGroup: JSXSlack.FC = props => { : undefined const element = ( - type="radio_buttons" action_id={props.actionId || props.name} options={options} diff --git a/src/block-kit/index.ts b/src/block-kit/index.ts index ecf77e28..ae3e0cf3 100644 --- a/src/block-kit/index.ts +++ b/src/block-kit/index.ts @@ -11,10 +11,13 @@ export { File } from './File' export { Image } from './Image' export { Input, Textarea } from './Input' export { Section, Field } from './Section' -export { Mrkdwn } from './composition/Mrkdwn' // Block elements export { Button } from './elements/Button' +export { CheckboxGroup, Checkbox } from './elements/Checkbox' +export { DatePicker } from './elements/DatePicker' +export { Overflow, OverflowItem } from './elements/Overflow' +export { RadioButtonGroup, RadioButton } from './elements/RadioButton' export { Select, SelectFragment, @@ -25,12 +28,10 @@ export { ConversationsSelect, ChannelsSelect, } from './elements/Select' -export { Overflow, OverflowItem } from './elements/Overflow' -export { DatePicker } from './elements/DatePicker' -export { RadioButtonGroup, RadioButton } from './elements/RadioButton' // PlainTextInput won't provide because Input block has an usage as component. // export { PlainTextInput } from './elements/PlainTextInput' // Composition objects export { Confirm } from './composition/Confirm' +export { Mrkdwn } from './composition/Mrkdwn' From 3116935eeedf215081d88b173d1d9722a993673f Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Fri, 7 Feb 2020 20:07:27 +0900 Subject: [PATCH 03/11] Add the basic test case of --- src/html.tsx | 1 + src/jsx.ts | 1 + .../block-elements/interactive-components.tsx | 99 ++++++++++++++++++- 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/html.tsx b/src/html.tsx index 99191f01..b20ab2b0 100644 --- a/src/html.tsx +++ b/src/html.tsx @@ -129,6 +129,7 @@ export const parse = ( return `${format}` } + case 'small': case 'span': case 'ul': case 'li': diff --git a/src/jsx.ts b/src/jsx.ts index 5566c343..03c2c526 100644 --- a/src/jsx.ts +++ b/src/jsx.ts @@ -165,6 +165,7 @@ export namespace JSXSlack { s: {} section: { id?: string; children: Children } select: IntrinsicProps + small: {} span: {} strike: {} strong: {} diff --git a/test/block-kit/block-elements/interactive-components.tsx b/test/block-kit/block-elements/interactive-components.tsx index 42a58c2f..7a5f42fa 100644 --- a/test/block-kit/block-elements/interactive-components.tsx +++ b/test/block-kit/block-elements/interactive-components.tsx @@ -3,6 +3,7 @@ import { ActionsBlock, Option as SlackOption, Overflow as SlackOverflow, + RadioButtons, SectionBlock, } from '@slack/types' import JSXSlack, { @@ -10,10 +11,13 @@ import JSXSlack, { Blocks, Button, ChannelsSelect, + CheckboxGroup, + Checkbox, Confirm, ConversationsSelect, DatePicker, ExternalSelect, + Fragment, Home, Modal, Optgroup, @@ -647,7 +651,7 @@ describe('Interactive components', () => { describe('', () => { it('outputs radio button group in actions block', () => { - const radioButtonAction = { + const radioButtonAction: RadioButtons = { type: 'radio_buttons', action_id: 'radio-buttons', options: [ @@ -808,4 +812,97 @@ describe('Interactive components', () => { ).toThrow(/incompatible/i) }) }) + + describe.only('', () => { + it('outputs checkbox group in actions block', () => { + const checkboxAction = { + type: 'checkboxes', + action_id: 'checkboxGroup', + options: [ + { + text: { type: 'mrkdwn', text: '*1st*', verbatim: true }, + description: { + type: 'mrkdwn', + text: 'The first option', + verbatim: true, + }, + value: 'first', + }, + { + text: { type: 'mrkdwn', text: '2nd', verbatim: true }, + description: { + type: 'mrkdwn', + text: 'The _second_ option', + verbatim: true, + }, + value: 'second', + }, + { + text: { type: 'mrkdwn', text: '3rd', verbatim: true }, + value: 'third', + }, + ], + initial_options: [ + { + text: { type: 'mrkdwn', text: '2nd', verbatim: true }, + description: { + type: 'mrkdwn', + text: 'The _second_ option', + verbatim: true, + }, + value: 'second', + }, + ], + } + + expect( + JSXSlack( + + + + + 1st + + + The second option + + } + > + 2nd + + 3rd + + + + ).blocks + ).toStrictEqual([action(checkboxAction)]) + + // Alternative ways + expect( + JSXSlack( + + + + + *1st* + The first option + + second, ' option']} + checked + > + 2nd + + 3rd + + + + ).blocks + ).toStrictEqual([action(checkboxAction)]) + }) + }) }) From 55850b948c125ac848c91b3e67ce702fcb86e32e Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Fri, 7 Feb 2020 20:40:51 +0900 Subject: [PATCH 04/11] Update demo schema to add checkbox components --- demo/schema.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/demo/schema.js b/demo/schema.js index a52a741e..fc6a500e 100644 --- a/demo/schema.js +++ b/demo/schema.js @@ -9,6 +9,7 @@ const blockInteractiveComponents = [ 'Overflow', 'DatePicker', 'RadioButtonGroup', + 'CheckboxGroup', // HTML compatible 'button', @@ -102,6 +103,7 @@ const schema = { 'ChannelsSelect', 'DatePicker', 'RadioButtonGroup', + 'CheckboxGroup', ], }, @@ -177,6 +179,7 @@ const schema = { 'ChannelsSelect', 'DatePicker', 'RadioButtonGroup', + 'CheckboxGroup', ], }, input: { @@ -197,6 +200,7 @@ const schema = { 'ChannelsSelect', 'DatePicker', 'RadioButtonGroup', + 'CheckboxGroup', ], }, @@ -313,6 +317,18 @@ const schema = { children: ['RadioButton'], }, RadioButton: { attrs: { value: null, description: null }, children: [] }, + CheckboxGroup: { + attrs: { + values: null, + ...blockInteractiveCommonAttrs, + ...inputComponentAttrs, + }, + children: ['Checkbox'], + }, + Checkbox: { + attrs: { value: null, checked: [], description: null }, + children: ['Mrkdwn', 'small', ...markupHTML], + }, // Composition objects Confirm: { @@ -385,6 +401,7 @@ const schema = { t => t !== 's' && t !== 'strike' && t !== 'del' ), }, + small: { attrs: {}, children: markupHTML }, span: { attrs: {}, children: markupHTML }, strike: { attrs: {}, From 81e695899ef8f8fae6ec7347abe4a77d0ea61482 Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sat, 8 Feb 2020 17:37:42 +0900 Subject: [PATCH 05/11] Redirect contents in small tag to description prop --- src/block-kit/Context.tsx | 2 +- src/block-kit/Section.tsx | 2 +- src/block-kit/composition/Mrkdwn.tsx | 13 ++----- src/block-kit/composition/utils.ts | 11 ++++-- src/block-kit/elements/Checkbox.tsx | 36 +++++++++++------ src/block-kit/elements/RadioButton.tsx | 39 ++++++++++--------- src/block-kit/elements/utils.ts | 31 +++++++++++++++ src/utils.tsx | 11 ++---- .../block-elements/interactive-components.tsx | 8 ++-- 9 files changed, 97 insertions(+), 56 deletions(-) create mode 100644 src/block-kit/elements/utils.ts diff --git a/src/block-kit/Context.tsx b/src/block-kit/Context.tsx index a3dc4003..0336174b 100644 --- a/src/block-kit/Context.tsx +++ b/src/block-kit/Context.tsx @@ -45,7 +45,7 @@ export const Context: JSXSlack.FC component if (props.type === mrkdwnSymbol) - return mrkdwn(props.text, props.verbatim) + return mrkdwn(html(props.children), props.verbatim) } return undefined diff --git a/src/block-kit/Section.tsx b/src/block-kit/Section.tsx index 3c7bca51..60d095f1 100644 --- a/src/block-kit/Section.tsx +++ b/src/block-kit/Section.tsx @@ -58,7 +58,7 @@ export const Section: JSXSlack.FC has unexpected component as an accessory.') } diff --git a/src/block-kit/composition/Mrkdwn.tsx b/src/block-kit/composition/Mrkdwn.tsx index 4fe89bde..fad5f837 100644 --- a/src/block-kit/composition/Mrkdwn.tsx +++ b/src/block-kit/composition/Mrkdwn.tsx @@ -1,7 +1,6 @@ /** @jsx JSXSlack.h */ import { JSXSlack } from '../../jsx' import { ObjectOutput } from '../../utils' -import html from '../../html' export const mrkdwnSymbol = Symbol('jsx-slack-mrkdwn-composition') @@ -10,16 +9,10 @@ interface MrkdwnProps { verbatim?: boolean } -interface MrkdwnComponentProps { +interface MrkdwnInternalProps extends MrkdwnProps { type: typeof mrkdwnSymbol - text: string - verbatim?: boolean } -export const Mrkdwn: JSXSlack.FC = ({ children, verbatim }) => ( - - type={mrkdwnSymbol} - text={html(children)} - verbatim={verbatim} - /> +export const Mrkdwn: JSXSlack.FC = props => ( + {...props} type={mrkdwnSymbol} /> ) diff --git a/src/block-kit/composition/utils.ts b/src/block-kit/composition/utils.ts index 4fc8aa4a..e2286592 100644 --- a/src/block-kit/composition/utils.ts +++ b/src/block-kit/composition/utils.ts @@ -21,11 +21,16 @@ export const mrkdwn = ( verbatim, }) -export const mrkdwnFromNode = (node: JSXSlack.Children<{}>): MrkdwnElement => { +export const mrkdwnFromNode = ( + node: JSXSlack.Children<{}>, + defaultOptions?: { + verbatim?: boolean + } +): MrkdwnElement => { const [child] = JSXSlack.normalizeChildren(node) if (typeof child === 'object' && child.props.type === mrkdwnSymbol) - return mrkdwn(child.props.text, child.props.verbatim) + return mrkdwn(html(child.props.children), child.props.verbatim) - return mrkdwn(html(node), true) + return mrkdwn(html(node), defaultOptions ? defaultOptions.verbatim : true) } diff --git a/src/block-kit/elements/Checkbox.tsx b/src/block-kit/elements/Checkbox.tsx index 580562d5..0b20abc2 100644 --- a/src/block-kit/elements/Checkbox.tsx +++ b/src/block-kit/elements/Checkbox.tsx @@ -1,5 +1,6 @@ /** @jsx JSXSlack.h */ import { PlainTextElement, MrkdwnElement } from '@slack/types' +import { findNode, pickInternalNodes } from './utils' import { ConfirmProps } from '../composition/Confirm' import { mrkdwnFromNode } from '../composition/utils' import { JSXSlack } from '../../jsx' @@ -31,25 +32,33 @@ export interface CheckboxProps { value: string } -interface CheckboxInternal extends CheckboxProps { +interface CheckboxInternalProps extends CheckboxProps { type: typeof checkboxInternal } -const filterCheckbox = children => - JSXSlack.normalizeChildren(children).filter( - c => - isNode(c) && - c.type === JSXSlack.NodeType.object && - c.props.type === checkboxInternal - ) as JSXSlack.Node[] +const toOptionObject = (props: CheckboxInternalProps): CheckboxOption => { + let description: JSXSlack.Children<{}> | undefined + const small = findNode(props.children, ({ type }) => type === 'small') + + if (small) { + description = small.children + small.children = [] + } -const toOptionObject = (props: CheckboxInternal): CheckboxOption => { const option: CheckboxOption = { text: mrkdwnFromNode(props.children), value: props.value, } - if (props.description) option.description = mrkdwnFromNode(props.description) + description = props.description || description + + if (description) + option.description = mrkdwnFromNode( + description, + option.text?.type === 'mrkdwn' + ? { verbatim: option.text.verbatim } + : undefined + ) return option } @@ -58,7 +67,10 @@ export const CheckboxGroup: JSXSlack.FC = props => { const states = new Map() const values = props.values || [] - const options = filterCheckbox(props.children).map(({ props: cProps }) => { + const options = pickInternalNodes( + checkboxInternal, + props.children as JSXSlack.Children + ).map(({ props: cProps }) => { if (cProps.value) { if (cProps.checked !== undefined) { states.set(cProps.value, !!cProps.checked) @@ -91,5 +103,5 @@ export const CheckboxGroup: JSXSlack.FC = props => { } export const Checkbox: JSXSlack.FC = props => ( - {...props} type={checkboxInternal} /> + {...props} type={checkboxInternal} /> ) diff --git a/src/block-kit/elements/RadioButton.tsx b/src/block-kit/elements/RadioButton.tsx index 95dc5441..3b7c2db9 100644 --- a/src/block-kit/elements/RadioButton.tsx +++ b/src/block-kit/elements/RadioButton.tsx @@ -1,16 +1,17 @@ /** @jsx JSXSlack.h */ import { Option, RadioButtons } from '@slack/types' +import { findNode, pickInternalNodes } from './utils' import { ConfirmProps } from '../composition/Confirm' import { plainText } from '../composition/utils' import { JSXSlack } from '../../jsx' -import { ObjectOutput, PlainText, isNode } from '../../utils' +import { ObjectOutput, PlainText } from '../../utils' import { WithInputProps, wrapInInput } from '../Input' const radioButtonInternal = Symbol('radioButtonInternal') interface RadioButtonGroupPropsBase { actionId?: string - children?: JSXSlack.Children + children?: JSXSlack.Children confirm?: JSXSlack.Node name?: string value?: string @@ -24,34 +25,35 @@ export interface RadioButtonProps { value: string } -interface RadioButtonInternal extends RadioButtonProps { - label: string +interface RadioButtonInternalProps extends RadioButtonProps { type: typeof radioButtonInternal } -const filterRadioButtons = children => - JSXSlack.normalizeChildren(children).filter( - c => - isNode(c) && - c.type === JSXSlack.NodeType.object && - c.props.type === radioButtonInternal - ) as JSXSlack.Node[] +const toOptionObject = (props: RadioButtonInternalProps): Option => { + let description: string | undefined + const small = findNode(props.children, ({ type }) => type === 'small') + + if (small) { + description = JSXSlack() + small.children = [] + } -const toOptionObject = (props: RadioButtonInternal): Option => { const option: Option = { - text: plainText(props.label), + text: plainText(JSXSlack(<PlainText children={props.children} />)), value: props.value, } - if (props.description) option.description = plainText(props.description) + description = props.description || description + if (description) option.description = plainText(description) return option } export const RadioButtonGroup: JSXSlack.FC<RadioButtonGroupProps> = props => { - const options = filterRadioButtons(props.children).map(radio => - toOptionObject(radio.props) - ) + const options = pickInternalNodes<RadioButtonInternalProps>( + radioButtonInternal, + props.children as JSXSlack.Children<RadioButtonInternalProps> + ).map(radio => toOptionObject(radio.props)) if (options.length === 0) throw new Error( @@ -76,9 +78,8 @@ export const RadioButtonGroup: JSXSlack.FC<RadioButtonGroupProps> = props => { } export const RadioButton: JSXSlack.FC<RadioButtonProps> = props => ( - <ObjectOutput<RadioButtonInternal> + <ObjectOutput<RadioButtonInternalProps> {...props} type={radioButtonInternal} - label={JSXSlack(<PlainText>{props.children}</PlainText>)} /> ) diff --git a/src/block-kit/elements/utils.ts b/src/block-kit/elements/utils.ts new file mode 100644 index 00000000..19cbf4ef --- /dev/null +++ b/src/block-kit/elements/utils.ts @@ -0,0 +1,31 @@ +import { JSXSlack } from '../../jsx' +import { isNode } from '../../utils' + +export const findNode = ( + nodes: JSXSlack.Children<{}>, + callback: (node: JSXSlack.Node) => boolean +): JSXSlack.Node | undefined => { + for (const node of JSXSlack.normalizeChildren(nodes)) { + if (isNode(node)) { + if (callback(node)) return node + + let ret = findNode(node.props.children, callback) + if (ret) return ret + + ret = findNode(node.children, callback) + if (ret) return ret + } + } + return undefined +} + +export const pickInternalNodes = <P extends { type: symbol }>( + symbol: P['type'], + children: JSXSlack.Children<P> +): JSXSlack.Node<P>[] => + JSXSlack.normalizeChildren(children).filter( + (c): c is JSXSlack.Node<P> => + isNode(c) && + c.type === JSXSlack.NodeType.object && + c.props.type === symbol + ) diff --git a/src/utils.tsx b/src/utils.tsx index 494e6615..f20be916 100644 --- a/src/utils.tsx +++ b/src/utils.tsx @@ -21,11 +21,9 @@ export enum SpecialLink { const spLinkMatcher = /^(#C|@[SUW])[A-Z0-9]{8}$/ -export function ArrayOutput<P = any>(props: { +export const ArrayOutput = <P extends {} = any>(props: { children: JSXSlack.Children<P> -}) { - return JSXSlack.h(JSXSlack.NodeType.array, props) -} +}) => JSXSlack.h(JSXSlack.NodeType.array, props) export const ObjectOutput = <P extends {} = any>(props: P) => JSXSlack.h(JSXSlack.NodeType.object, props) @@ -46,13 +44,12 @@ export function wrap<T>(children: T | T[]): T[] { return [] } -export const aliasTo = (component: JSXSlack.FC<any>, node: JSXSlack.Node) => { - return JSXSlack.h( +export const aliasTo = (component: JSXSlack.FC<any>, node: JSXSlack.Node) => + JSXSlack.h( component, node.props, ...wrap(node.props.children || node.children) ) -} export function detectSpecialLink(href: string): SpecialLink | undefined { if (href === '@channel') return SpecialLink.ChannelMention diff --git a/test/block-kit/block-elements/interactive-components.tsx b/test/block-kit/block-elements/interactive-components.tsx index 7a5f42fa..8fe4112e 100644 --- a/test/block-kit/block-elements/interactive-components.tsx +++ b/test/block-kit/block-elements/interactive-components.tsx @@ -721,11 +721,13 @@ describe('Interactive components', () => { </Confirm> } > - <RadioButton value="first" description="The first option"> + <RadioButton value="first"> 1st + <small>The first option</small> </RadioButton> - <RadioButton value="second" description="The second option"> + <RadioButton value="second"> 2nd + <small>The second option</small> </RadioButton> <RadioButton value="third">3rd</RadioButton> </RadioButtonGroup> @@ -813,7 +815,7 @@ describe('Interactive components', () => { }) }) - describe.only('<CheckboxGroup>', () => { + describe('<CheckboxGroup>', () => { it('outputs checkbox group in actions block', () => { const checkboxAction = { type: 'checkboxes', From 34533c572d09c5663441697e28e711203aadc125 Mon Sep 17 00:00:00 2001 From: Yuki Hattori <yukihattori1116@gmail.com> Date: Sun, 9 Feb 2020 02:51:40 +0900 Subject: [PATCH 06/11] Add test case about detailed behaviors of checkbox --- .../input-components-for-modal.tsx | 7 + .../block-elements/interactive-components.tsx | 192 ++++++++++++++++++ test/index.tsx | 7 +- test/tag.tsx | 22 +- 4 files changed, 216 insertions(+), 12 deletions(-) diff --git a/test/block-kit/block-elements/input-components-for-modal.tsx b/test/block-kit/block-elements/input-components-for-modal.tsx index 71308dad..2bc4858e 100644 --- a/test/block-kit/block-elements/input-components-for-modal.tsx +++ b/test/block-kit/block-elements/input-components-for-modal.tsx @@ -2,6 +2,8 @@ import { InputBlock, View } from '@slack/types' import JSXSlack, { ChannelsSelect, + CheckboxGroup, + Checkbox, ConversationsSelect, DatePicker, ExternalSelect, @@ -35,6 +37,11 @@ describe('Input components for modal', () => { <RadioButton value="test">test</RadioButton> </RadioButtonGroup> ), + props => ( + <CheckboxGroup {...props}> + <Checkbox value="test">test</Checkbox> + </CheckboxGroup> + ), ]) { expect( JSXSlack( diff --git a/test/block-kit/block-elements/interactive-components.tsx b/test/block-kit/block-elements/interactive-components.tsx index 8fe4112e..fc301c9c 100644 --- a/test/block-kit/block-elements/interactive-components.tsx +++ b/test/block-kit/block-elements/interactive-components.tsx @@ -20,6 +20,7 @@ import JSXSlack, { Fragment, Home, Modal, + Mrkdwn, Optgroup, Option, Overflow, @@ -905,6 +906,197 @@ describe('Interactive components', () => { </Home> ).blocks ).toStrictEqual([action(checkboxAction)]) + + // confirm prop in <Modal> + expect( + JSXSlack( + <Modal title="modal"> + <Actions blockId="actions"> + <CheckboxGroup + actionId="checkboxGroup" + confirm={ + <Confirm title="a" confirm="b" deny="c"> + foobar + </Confirm> + } + > + <Checkbox value="first"> + <Mrkdwn verbatim> + <b>1st</b> + <small>The first option</small> + </Mrkdwn> + </Checkbox> + <Checkbox value="second" checked> + 2nd + <small> + <Mrkdwn verbatim> + The <i>second</i> option + </Mrkdwn> + </small> + </Checkbox> + <Checkbox value="third">3rd</Checkbox> + </CheckboxGroup> + </Actions> + </Modal> + ).blocks + ).toStrictEqual([ + action({ + ...checkboxAction, + confirm: { + title: { type: 'plain_text', text: 'a', emoji: true }, + confirm: { type: 'plain_text', text: 'b', emoji: true }, + deny: { type: 'plain_text', text: 'c', emoji: true }, + text: { type: 'mrkdwn', text: 'foobar', verbatim: true }, + }, + } as any), + ]) + }) + + it('outputs checkbox group in section block', () => { + const [section]: SectionBlock[] = JSXSlack( + <Home> + <Section> + test + <CheckboxGroup> + <Checkbox value="a">A</Checkbox> + </CheckboxGroup> + </Section> + </Home> + ).blocks + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expect(section.accessory!.type).toBe('checkboxes') + }) + + it('throws error when <CheckboxGroup> has not contained <Checkbox>', () => { + expect(() => + JSXSlack( + <Home> + <Actions> + <CheckboxGroup>{}</CheckboxGroup> + </Actions> + </Home> + ) + ).toThrow(/must include/i) + + expect(() => + JSXSlack( + <Home> + <Actions> + <CheckboxGroup> + <Option value="wtf">I'm not checkbox</Option> + </CheckboxGroup> + </Actions> + </Home> + ) + ).toThrow(/must include/i) }) + + it('throws error when using <CheckboxGroup> within <Blocks> container', () => { + expect(() => + JSXSlack( + <Blocks> + <Section> + test + <CheckboxGroup> + <Checkbox value="a">A</Checkbox> + </CheckboxGroup> + </Section> + </Blocks> + ) + ).toThrow(/incompatible/i) + + expect(() => + JSXSlack( + <Blocks> + <Actions> + <CheckboxGroup> + <Checkbox value="a">A</Checkbox> + </CheckboxGroup> + </Actions> + </Blocks> + ) + ).toThrow(/incompatible/i) + }) + + it('prefers description prop of <Checkbox> rather than the content in <small> element', () => { + const [section] = JSXSlack( + <Home> + <Section> + test + <CheckboxGroup> + <Checkbox value="hello" description="foo"> + Hello! + <small>bar</small> + </Checkbox> + </CheckboxGroup> + </Section> + </Home> + ).blocks + + const [option] = section.accessory.options + expect(option.description.text).toBe('foo') + }) + + it("inherits content's <Mrkdwn> option into description", () => { + const [section] = JSXSlack( + <Home> + <Section> + test + <CheckboxGroup> + <Checkbox value="regular" description="description"> + Content + </Checkbox> + <Checkbox value="inherited" description="description"> + <Mrkdwn verbatim={false}>Content</Mrkdwn> + </Checkbox> + <Checkbox + value="mixed" + description={<Mrkdwn verbatim={false}>description</Mrkdwn>} + > + <Mrkdwn>Content</Mrkdwn> + </Checkbox> + <Checkbox value="small-mixed"> + <Mrkdwn>Content</Mrkdwn> + <small> + <Mrkdwn verbatim={false}>description</Mrkdwn> + </small> + </Checkbox> + </CheckboxGroup> + </Section> + </Home> + ).blocks + + const [regular, inherited, mixed, smallMixed] = section.accessory.options + expect(regular.description.verbatim).toBe(true) + expect(inherited.description.verbatim).toBe(false) + expect(mixed.text.verbatim).toBeUndefined() + expect(mixed.description.verbatim).toBe(false) + expect(smallMixed.text.verbatim).toBeUndefined() + expect(smallMixed.description.verbatim).toBe(false) + }) + }) + + it('prefers checked attribute in <Checkbox> rather than value prop in <CheckboxGroup>', () => { + const [section] = JSXSlack( + <Home> + <Section> + test + <CheckboxGroup values={['b', 'd']}> + <Checkbox value="a">A</Checkbox> + <Checkbox value="b">B</Checkbox> + <Checkbox value="c" checked={true}> + C + </Checkbox> + <Checkbox value="d" checked={false}> + D + </Checkbox> + </CheckboxGroup> + </Section> + </Home> + ).blocks + + const values = section.accessory.initial_options.map(opt => opt.value) + expect(values).toStrictEqual(['b', 'c']) }) }) diff --git a/test/index.tsx b/test/index.tsx index 4d83c0a3..e03ca7c3 100644 --- a/test/index.tsx +++ b/test/index.tsx @@ -5,5 +5,10 @@ beforeEach(() => JSXSlack.exactMode(false)) describe('#JSXSlack', () => { it('throws error by passed invalid node', () => - expect(() => JSXSlack({ props: {}, type: -1 } as any)).toThrow()) + expect(() => JSXSlack({ props: {}, type: -1, children: [] })).toThrow()) + + it('throws error when using not supported HTML element in JSX', () => + expect(() => + JSXSlack({ props: {}, type: 'center', children: [] }) + ).toThrow()) }) diff --git a/test/tag.tsx b/test/tag.tsx index 4d7899d5..2b0e388f 100644 --- a/test/tag.tsx +++ b/test/tag.tsx @@ -6,10 +6,10 @@ import JSXSlack, { Divider, Fragment, Image, - jsxslack, - Option as BlockKitOption, + Option, Section, - Select as BlockKitSelect, + Select, + jsxslack, } from '../src/index' describe('Tagged template', () => { @@ -49,11 +49,11 @@ describe('Tagged template', () => { <Divider /> <Actions> <Button actionId={`clap${count}`}>:clap: {count}</Button> - <BlockKitSelect actionId="select"> - <BlockKitOption value="1">one</BlockKitOption> - <BlockKitOption value="2">two</BlockKitOption> - <BlockKitOption value="3">three</BlockKitOption> - </BlockKitSelect> + <Select actionId="select"> + <Option value="1">one</Option> + <Option value="2">two</Option> + <Option value="3">three</Option> + </Select> </Actions> </Blocks> ) @@ -78,11 +78,11 @@ describe('Tagged template', () => { JSXSlack( <Blocks> <Actions> - <BlockKitSelect> + <Select> {[...Array(10)].map((_, i) => ( - <BlockKitOption value={i.toString()}>{i}</BlockKitOption> + <Option value={i.toString()}>{i}</Option> ))} - </BlockKitSelect> + </Select> </Actions> </Blocks> ) From 469cb54b4081d9d478d3da0e168c243a261bf075 Mon Sep 17 00:00:00 2001 From: Yuki Hattori <yukihattori1116@gmail.com> Date: Sun, 9 Feb 2020 02:57:03 +0900 Subject: [PATCH 07/11] Revert change for exist test case --- test/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.tsx b/test/index.tsx index e03ca7c3..a927df7a 100644 --- a/test/index.tsx +++ b/test/index.tsx @@ -5,7 +5,7 @@ beforeEach(() => JSXSlack.exactMode(false)) describe('#JSXSlack', () => { it('throws error by passed invalid node', () => - expect(() => JSXSlack({ props: {}, type: -1, children: [] })).toThrow()) + expect(() => JSXSlack({ props: {}, type: -1 } as any)).toThrow()) it('throws error when using not supported HTML element in JSX', () => expect(() => From 4aaeb3efdaedfff4e7415fa599b1e725eb45091a Mon Sep 17 00:00:00 2001 From: Yuki Hattori <yukihattori1116@gmail.com> Date: Sun, 9 Feb 2020 03:02:13 +0900 Subject: [PATCH 08/11] Remove unnecessary condition --- src/block-kit/elements/Checkbox.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/block-kit/elements/Checkbox.tsx b/src/block-kit/elements/Checkbox.tsx index 0b20abc2..baa3a639 100644 --- a/src/block-kit/elements/Checkbox.tsx +++ b/src/block-kit/elements/Checkbox.tsx @@ -10,9 +10,9 @@ import { WithInputProps, wrapInInput } from '../Input' const checkboxInternal = Symbol('checkboxInternal') interface CheckboxOption { - text: PlainTextElement | MrkdwnElement + text: MrkdwnElement // jsx-slack alaways uses mrkdwn element value?: string - description?: PlainTextElement | MrkdwnElement + description?: MrkdwnElement } interface CheckboxGroupPropsBase { @@ -53,12 +53,9 @@ const toOptionObject = (props: CheckboxInternalProps): CheckboxOption => { description = props.description || description if (description) - option.description = mrkdwnFromNode( - description, - option.text?.type === 'mrkdwn' - ? { verbatim: option.text.verbatim } - : undefined - ) + option.description = mrkdwnFromNode(description, { + verbatim: option.text.verbatim, + }) return option } From b2973952f426d95f79c3c6eb1dd859e98eadbc02 Mon Sep 17 00:00:00 2001 From: Yuki Hattori <yukihattori1116@gmail.com> Date: Mon, 10 Feb 2020 01:45:27 +0900 Subject: [PATCH 09/11] [ci skip] Add documentations about checkbox --- docs/block-elements.md | 111 ++++++++++++++++++++++++--- docs/html-like-formatting.md | 2 +- docs/jsx-components-for-block-kit.md | 2 + docs/layout-blocks.md | 4 +- 4 files changed, 106 insertions(+), 13 deletions(-) diff --git a/docs/block-elements.md b/docs/block-elements.md index 7827c7ec..f28dd75e 100644 --- a/docs/block-elements.md +++ b/docs/block-elements.md @@ -379,6 +379,73 @@ An easy way to let the user selecting any date is using `<DatePicker>` component - `title`/ `hint` (optional): Specify a helpful text appears under the element. - `required` (optional): A boolean prop to specify whether any value must be filled when user confirms modal. +### <a name="checkbox-group" id="checkbox-group"></a> [`<CheckboxGroup>`: Checkbox group](https://api.slack.com/reference/block-kit/block-elements#checkboxes) (Only for modal and home tab) + +A container for grouping checkboxes. _This component is only for [`<Modal>`](block-containers.md#modal) and [`<Home>`](block-containers.md#home) container. It cannot use in [`<Blocks>`](block-containers.md#blocks) container for messaging._ + +<!-- TODO: Add example --> + +#### Props + +- `name` / `actionId` (optional): An identifier for the action. +- `values` (optional): An array of value for initially selected checkboxes. They must match to `value` property in `<Checkbox>` elements in children. +- `confirm` (optional): [`<Confirm>` element](#confirm) to show confirmation dialog. + +#### As [an input component for modal](#input-components-for-modal) + +<!-- TODO: Add example --> + +##### Props for modal's input + +- `label` (**required**): The label string for the group. +- `id` / `blockId` (optional): A string of unique identifier of [`<Input>` layout block](layout-blocks.md#input). +- `title`/ `hint` (optional): Specify a helpful text appears under the group. +- `required` (optional): A boolean prop to specify whether any value must be filled when user confirms modal. + +### <a name="checkbox" id="checkbox"></a> `<Checkbox>`: Checkbox + +A checkbox item. It must place in the children of `<CheckboxGroup>`. + +It supports raw [mrkdwn format](https://api.slack.com/reference/surfaces/formatting) / [HTML-like formatting](./html-like-formatting.md) in the both of contents and `description` property. + +```jsx +<Checkbox + value="checkbox" + description={ + <Fragment> + XXX-1234 - <i>by Yuki Hattori</i> + </Fragment> + } +> + <b>Checkbox item</b>: foobar +</Checkbox> +``` + +[<img src="https://raw.githubusercontent.com/speee/jsx-slack/master/docs/preview-btn.svg?sanitize=true" width="240" />](https://api.slack.com/tools/block-kit-builder?mode=appHome&view=%7B%22type%22%3A%22home%22%2C%22blocks%22%3A%5B%7B%22type%22%3A%22actions%22%2C%22elements%22%3A%5B%7B%22type%22%3A%22checkboxes%22%2C%22options%22%3A%5B%7B%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22*Checkbox%20item*%3A%20foobar%22%2C%22verbatim%22%3Atrue%7D%2C%22value%22%3A%22checkbox%22%2C%22description%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22XXX-1234%20-%20_by%20Yuki%20Hattori_%22%2C%22verbatim%22%3Atrue%7D%7D%5D%7D%5D%7D%5D%7D) + +> :information_source: [Links and mentions through `<a>` tag](https://github.com/speee/jsx-slack/blob/master/docs/html-like-formatting.md#links) will be ignored by Slack. + +#### Props + +- `value` (**required**): A string value to send to Slack App when choosing the checkbox. +- `description` (optional): A description string or JSX element for the current checkbox. It can see with faded color just below the main label. `<Checkbox>` prefers this prop than redirection by `<small>`. +- `checked` (optional): A boolean value indicating the initial state of the checkbox. If it's not defined, the initial state is following `values` property in the parent `<CheckboxGroup>`. + +#### Redirect `<small>` into description + +`<Checkbox>` allows `<small>` element for ergonomic templating, to redirect the content into description when `description` prop is not defined. + +A below checkbox is meaning exactly the same as an example shown earlier. + +```jsx +<Checkbox value="checkbox"> + <b>Checkbox item</b>: foobar + <small> + XXX-1234 - <i>by Yuki Hattori</i> + </small> +</Checkbox> +``` + ### <a name="radio-button-group" id="radio-button-group"></a> [`<RadioButtonGroup>`: Radio button group](https://api.slack.com/reference/block-kit/block-elements#radio) (Only for modal and home tab) A container for grouping radio buttons. It provides easy control of the selected option through similar interface to [`<Select>`](#select). @@ -412,7 +479,7 @@ _This component is only for [`<Modal>`](block-containers.md#modal) and [`<Home>` #### Props - `name` / `actionId` (optional): An identifier for the action. -- `value` (optional): A value for initial selected option. It must choose value from defined `<RadioButton>` elements in children. +- `value` (optional): A value for initially selected option. It must match to `value` property in one of `<RadioButton>` elements in children. - `confirm` (optional): [`<Confirm>` element](#confirm) to show confirmation dialog. #### As [an input component for modal](#input-components-for-modal) @@ -429,17 +496,13 @@ In `<Modal>` container, `<RadioButtonGroup>` can place as `<Modal>`'s direct chi value="all" required > - <RadioButton - value="all" - description="Notify all received events every time." - > + <RadioButton value="all"> All events + <small>Notify all received events every time.</small> </RadioButton> - <RadioButton - value="summary" - description="Send a daily summary at AM 9:30 every day." - > + <RadioButton value="summary"> Daily summary + <small>Send a daily summary at AM 9:30 every day.</small> </RadioButton> <RadioButton value="off">Off</RadioButton> </RadioButtonGroup> @@ -458,10 +521,35 @@ In `<Modal>` container, `<RadioButtonGroup>` can place as `<Modal>`'s direct chi ### <a name="radio-button" id="radio-button"></a> `<RadioButton>`: Radio button +An item of the radio button. It must place in the children of `<RadioButtonGroup>`. + +_Unlike checkbox, the content of text and description cannot style via [mrkdwn format](https://api.slack.com/reference/surfaces/formatting) and [HTML elements](./html-like-formatting.md) because Slack only allows plain text in the radio button._ + #### Props -- `value` (**required**): A string value to send to Slack App when choose option. -- `description` (optional): A description text for current radio button. It can see with faded color in just below main label. +- `value` (**required**): A string value to send to Slack App when choosing the radio button. +- `description` (optional): A description text for the current radio button. It can see with faded color just below the main label. `<RadioButton>` prefers this prop than redirection by `<small>`. + +#### Redirect `<small>` into description + +`<RadioButton>` allows `<small>` element for ergonomic templating, to redirect the text content into description when `description` prop is not defined. + +For example, below 2 elements are meaning exactly the same radio button. + +```jsx +<RadioButtonGroup> + {/* Define description through prop */} + <RadioButton value="radio" description="Description"> + Radio button + </RadioButton> + + {/* Define description through <small> element */} + <RadioButton value="radio"> + Radio button + <small>Description</small> + </RadioButton> +</RadioButtonGroup> +``` ## [Composition objects](https://api.slack.com/reference/messaging/composition-objects) @@ -572,6 +660,7 @@ The list of input components is following: - [`<ConversationsSelect>`](#conversations-select) - [`<ChannelsSelect>`](#channels-select) - [`<DatePicker>`](#date-picker) +- [`<CheckboxGroup>`](#checkbox-group) - [`<RadioButtonGroup>`](#radio-button-group) ### <a name="input" id="input"></a> [`<Input>`: Plain-text input element](https://api.slack.com/reference/block-kit/block-elements#input) diff --git a/docs/html-like-formatting.md b/docs/html-like-formatting.md index d3d50b1c..6879d608 100644 --- a/docs/html-like-formatting.md +++ b/docs/html-like-formatting.md @@ -8,7 +8,7 @@ jsx-slack has HTML-compatible JSX elements to format messages. It might be verbo _Using HTML elements is not mandatory. You may also use [a regular mrkdwn syntax][mrkdwn] to format if necessary._ -[mrkdwn]: https://api.slack.com/docs/message-formatting +[mrkdwn]: https://api.slack.com/reference/surfaces/formatting ## Format text style diff --git a/docs/jsx-components-for-block-kit.md b/docs/jsx-components-for-block-kit.md index fb838b5f..abedfcdd 100644 --- a/docs/jsx-components-for-block-kit.md +++ b/docs/jsx-components-for-block-kit.md @@ -35,6 +35,8 @@ - [**`<Overflow>`**: Overflow menu](block-elements.md#overflow) - [**`<OverflowItem>`**: Menu item in overflow menu](block-elements.md#overflow-item) - [**`<DatePicker>`**: Select date from calendar](block-elements.md#date-picker) +- [**`<CheckboxGroup>`**: Checkbox group](block-elements.md#checkbox-group) (Only for modal and home tab) + - [**`<Checkbox>`**: Checkbox](block-elements.md#checkbox) - [**`<RadioButtonGroup>`**: Radio button group](block-elements.md#radio-button-group) (Only for modal and home tab) - [**`<RadioButton>`**: Radio button](block-elements.md#radio-button) diff --git a/docs/layout-blocks.md b/docs/layout-blocks.md index d0bb4cea..e7414427 100644 --- a/docs/layout-blocks.md +++ b/docs/layout-blocks.md @@ -28,7 +28,7 @@ Display a simple text message. You have to specify the content as children. It a ### Accessory -A one of accessory component may include as the children of `<Section>`. The defined element will show in side-by-side of text. +A one of accessory component may include as the children of `<Section>`. The defined element will show in side-by-side or just below of text. ```jsx <Blocks> @@ -52,6 +52,7 @@ A one of accessory component may include as the children of `<Section>`. The def - [`<ChannelsSelect>`](block-elements.md#channels-select) - [`<Overflow>`](block-elements.md#overflow) - [`<DatePicker>`](block-elements.md#date-picker) +- [`<CheckboxGroup>`](block-elements.md#checkbox-group) (Only for [`<Modal>`](block-containers.md#modal) and [`<Home>`](block-containers.md#home) container) - [`<RadioButtonGroup>`](block-elements.md#radio-button-group) (Only for [`<Modal>`](block-containers.md#modal) and [`<Home>`](block-containers.md#home) container) ### <a name="field" id="field"></a> `<Field>`: Fields for section block @@ -218,6 +219,7 @@ If you want to use `<Input>` as layout block, you have to place one of [availabl - [`<ConversationsSelect>`](block-elements.md#conversations-select) - [`<ChannelsSelect>`](block-elements.md#channels-select) - [`<DatePicker>`](block-elements.md#date-picker) +- [`<CheckboxGroup>`](block-elements.md#checkbox-group) - [`<RadioButtonGroup>`](block-elements.md#radio-button-group) ### Note From 9675d3a85b6f4e43072a188a4ae12f9a5a6d38fb Mon Sep 17 00:00:00 2001 From: Yuki Hattori <yuki.hattori@speee.jp> Date: Mon, 10 Feb 2020 11:27:28 +0900 Subject: [PATCH 10/11] [ci skip] Add examples about checkboxes --- docs/block-elements.md | 68 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/docs/block-elements.md b/docs/block-elements.md index f28dd75e..cb1ca35f 100644 --- a/docs/block-elements.md +++ b/docs/block-elements.md @@ -383,7 +383,46 @@ An easy way to let the user selecting any date is using `<DatePicker>` component A container for grouping checkboxes. _This component is only for [`<Modal>`](block-containers.md#modal) and [`<Home>`](block-containers.md#home) container. It cannot use in [`<Blocks>`](block-containers.md#blocks) container for messaging._ -<!-- TODO: Add example --> +```jsx +<Home> + <Section> + <b>ToDo List</b> + <CheckboxGroup actionId="todo"> + <Checkbox value="xxx-0001"> + <b>Learn about Slack app</b> ( + <time datetime={new Date(2020, 1, 24)}>{'{date}'}</time>) + <small> + <i> + XXX-0001: <b>High</b> + </i> + </small> + </Checkbox> + <Checkbox value="xxx-0002"> + <b>Learn about jsx-slack</b> ( + <time datetime={new Date(2020, 1, 27)}>{'{date}'}</time>) + <small> + <i> + XXX-0002: <b>Medium</b> + </i> + </small> + </Checkbox> + <Checkbox value="xxx-0003" checked> + <s> + <b>Prepare development environment</b> ( + <time datetime={new Date(2020, 1, 21)}>{'{date}'}</time>) + </s> + <small> + <i> + XXX-0003: <b>Medium</b> + </i> + </small> + </Checkbox> + </CheckboxGroup> + </Section> +</Home> +``` + +[<img src="https://raw.githubusercontent.com/speee/jsx-slack/master/docs/preview-btn.svg?sanitize=true" width="240" />](<https://api.slack.com/tools/block-kit-builder?mode=appHome&view=%7B%22type%22%3A%22home%22%2C%22blocks%22%3A%5B%7B%22type%22%3A%22section%22%2C%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22*ToDo%20List*%22%2C%22verbatim%22%3Atrue%7D%2C%22accessory%22%3A%7B%22type%22%3A%22checkboxes%22%2C%22action_id%22%3A%22todo%22%2C%22options%22%3A%5B%7B%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22*Learn%20about%20Slack%20app*%20(%3C!date%5E1582470000%5E%7Bdate%7D%7CFebruary%2023rd%2C%202020%3E)%22%2C%22verbatim%22%3Atrue%7D%2C%22value%22%3A%22xxx-0001%22%2C%22description%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22_XXX-0001%3A%20*High*_%22%2C%22verbatim%22%3Atrue%7D%7D%2C%7B%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22*Learn%20about%20jsx-slack*%20(%3C!date%5E1582729200%5E%7Bdate%7D%7CFebruary%2026th%2C%202020%3E)%22%2C%22verbatim%22%3Atrue%7D%2C%22value%22%3A%22xxx-0002%22%2C%22description%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22_XXX-0002%3A%20*Medium*_%22%2C%22verbatim%22%3Atrue%7D%7D%2C%7B%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22~*Prepare%20development%20environment*%20(%3C!date%5E1582210800%5E%7Bdate%7D%7CFebruary%2020th%2C%202020%3E)~%22%2C%22verbatim%22%3Atrue%7D%2C%22value%22%3A%22xxx-0003%22%2C%22description%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22_XXX-0003%3A%20*Medium*_%22%2C%22verbatim%22%3Atrue%7D%7D%5D%2C%22initial_options%22%3A%5B%7B%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22~*Prepare%20development%20environment*%20(%3C!date%5E1582210800%5E%7Bdate%7D%7CFebruary%2020th%2C%202020%3E)~%22%2C%22verbatim%22%3Atrue%7D%2C%22value%22%3A%22xxx-0003%22%2C%22description%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22_XXX-0003%3A%20*Medium*_%22%2C%22verbatim%22%3Atrue%7D%7D%5D%7D%7D%5D%7D>) #### Props @@ -393,7 +432,30 @@ A container for grouping checkboxes. _This component is only for [`<Modal>`](blo #### As [an input component for modal](#input-components-for-modal) -<!-- TODO: Add example --> +```jsx +<Modal title="Quick survey"> + <CheckboxGroup + id="foods" + name="foods" + label="What do you want to eat for the party in this Friday?" + required + > + <Checkbox value="burger">Burger :hamburger:</Checkbox> + <Checkbox value="pizza">Pizza :pizza:</Checkbox> + <Checkbox value="taco">Tex-Mex taco :taco:</Checkbox> + <Checkbox value="sushi">Sushi :sushi:</Checkbox> + <Checkbox value="others"> + Others + <small> + <i>Let me know in the below form.</i> + </small> + </Checkbox> + </CheckboxGroup> + <Input type="text" id="others" name="others" label="What do you want?" /> +</Modal> +``` + +[<img src="https://raw.githubusercontent.com/speee/jsx-slack/master/docs/preview-btn.svg?sanitize=true" width="240" />](https://api.slack.com/tools/block-kit-builder?mode=modal&view=%7B%22type%22%3A%22modal%22%2C%22title%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22Quick%20survey%22%2C%22emoji%22%3Atrue%7D%2C%22submit%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22Submit%22%2C%22emoji%22%3Atrue%7D%2C%22blocks%22%3A%5B%7B%22type%22%3A%22input%22%2C%22block_id%22%3A%22foods%22%2C%22label%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22What%20do%20you%20want%20to%20eat%20for%20the%20party%20in%20this%20Friday%3F%22%2C%22emoji%22%3Atrue%7D%2C%22optional%22%3Afalse%2C%22element%22%3A%7B%22type%22%3A%22checkboxes%22%2C%22action_id%22%3A%22foods%22%2C%22options%22%3A%5B%7B%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22Burger%20%3Ahamburger%3A%22%2C%22verbatim%22%3Atrue%7D%2C%22value%22%3A%22burger%22%7D%2C%7B%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22Pizza%20%3Apizza%3A%22%2C%22verbatim%22%3Atrue%7D%2C%22value%22%3A%22pizza%22%7D%2C%7B%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22Tex-Mex%20taco%20%3Ataco%3A%22%2C%22verbatim%22%3Atrue%7D%2C%22value%22%3A%22taco%22%7D%2C%7B%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22Sushi%20%3Asushi%3A%22%2C%22verbatim%22%3Atrue%7D%2C%22value%22%3A%22sushi%22%7D%2C%7B%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22Others%22%2C%22verbatim%22%3Atrue%7D%2C%22value%22%3A%22others%22%2C%22description%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22_Let%20me%20know%20in%20the%20below%20form._%22%2C%22verbatim%22%3Atrue%7D%7D%5D%7D%7D%2C%7B%22type%22%3A%22input%22%2C%22block_id%22%3A%22others%22%2C%22label%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22What%20do%20you%20want%3F%22%2C%22emoji%22%3Atrue%7D%2C%22optional%22%3Atrue%2C%22element%22%3A%7B%22type%22%3A%22plain_text_input%22%2C%22action_id%22%3A%22others%22%7D%7D%5D%7D) ##### Props for modal's input @@ -634,6 +696,8 @@ Setting `verbatim` to `false` will tell Slack to auto-convert links, conversatio [<img src="https://raw.githubusercontent.com/speee/jsx-slack/master/docs/preview-btn.svg?sanitize=true" width="240" />](https://api.slack.com/tools/block-kit-builder?mode=message&blocks=%5B%7B%22type%22%3A%22section%22%2C%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22https%3A%2F%2Fexample.com%2F%22%2C%22verbatim%22%3Afalse%7D%7D%2C%7B%22type%22%3A%22section%22%2C%22fields%22%3A%5B%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22%23general%22%2C%22verbatim%22%3Afalse%7D%5D%7D%2C%7B%22type%22%3A%22context%22%2C%22elements%22%3A%5B%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22%40here%22%2C%22verbatim%22%3Afalse%7D%2C%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22Hello!%22%2C%22verbatim%22%3Atrue%7D%5D%7D%2C%7B%22type%22%3A%22actions%22%2C%22elements%22%3A%5B%7B%22type%22%3A%22button%22%2C%22text%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22Button%22%2C%22emoji%22%3Atrue%7D%2C%22confirm%22%3A%7B%22title%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22Commit%20your%20action%22%2C%22emoji%22%3Atrue%7D%2C%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22*%40here*%20Are%20you%20sure%3F%22%2C%22verbatim%22%3Afalse%7D%2C%22confirm%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22Yes%2C%20please%22%2C%22emoji%22%3Atrue%7D%2C%22deny%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22Cancel%22%2C%22emoji%22%3Atrue%7D%7D%7D%5D%7D%5D) +<!-- INFO: We have no example in checkbox due to meaningless: Slack will be ignored links and mentions in checkbox. --> + #### Note _Slack recommends disabling automatic parsing on text composition components and they have made it clear that they might deprecate this feature in the future._ More information can be found [here](https://api.slack.com/reference/surfaces/formatting#why_you_should_consider_disabling_automatic_parsing). From 04c40bd1aaa66d593476521bcc208f1c0d155978 Mon Sep 17 00:00:00 2001 From: Yuki Hattori <yuki.hattori@speee.jp> Date: Mon, 10 Feb 2020 11:33:11 +0900 Subject: [PATCH 11/11] [ci skip] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fe26ac8..9382d51e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## [Unreleased] +### Added + +- [`<CheckboxGroup>`](https://github.com/speee/jsx-slack/blob/master/docs/block-elements.md#checkbox-group) and [`<Checkbox>`](https://github.com/speee/jsx-slack/blob/master/docs/block-elements.md#checkbox) interactive component ([#108](https://github.com/speee/jsx-slack/issues/108), [#109](https://github.com/speee/jsx-slack/pull/109)) +- [Redirect the content of `<small>` element into `description`](https://github.com/speee/jsx-slack/blob/master/docs/block-elements.md#redirect-small-into-description) in `<Checkbox>` and `<RadioButton>` ([#109](https://github.com/speee/jsx-slack/pull/109)) + ### Changed - Upgrade dependent packages to the latest version ([#107](https://github.com/speee/jsx-slack/pull/107))