Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add input-compatible props to select-like elements and DatePicker #63

Merged
merged 4 commits into from
Oct 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- `<Modal>` container component ([#60](https://github.com/speee/jsx-slack/pull/60))
- `<Input>` component for layout block and block element ([#61](https://github.com/speee/jsx-slack/pull/61))
- `<Textarea>` component ([#62](https://github.com/speee/jsx-slack/pull/62))
- Input-compatible props to select-like elements and `<DatePicker>` ([#63](https://github.com/speee/jsx-slack/pull/63))

### Changed

Expand Down
112 changes: 101 additions & 11 deletions docs/jsx-components-for-block-kit.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ If you want to use `<Input>` as layout block, you have to place the one of avail

```jsx
<Modal title="My App">
<Input label="User" hint="Please select one of users." required>
<Input label="User" title="Please select one of users." required>
<UsersSelect placeholder="Choose user..." />
</Input>
</Modal>
Expand All @@ -257,7 +257,7 @@ If you want to use `<Input>` as layout block, you have to place the one of avail

- `label` (**required**): The label string for the element.
- `id` / `blockId` (optional): A string of unique identifier of block.
- `hint` (optional): Specify a helpful text appears under the element.
- `title`/ `hint` (optional): Specify a helpful text appears under the element. `title` is alias to `hint` prop for keeping HTML compatibility.
- `required` (optional): A boolean prop to specify whether any value must be filled when user confirms modal. `false` by default for HTML compatibility, and _notice that it is different from Slack's default._

#### Available elements as a child
Expand All @@ -267,8 +267,7 @@ If you want to use `<Input>` as layout block, you have to place the one of avail
- [`<UsersSelect>`](#usersselect-select-menu-with-user-list)
- [`<ConversationsSelect>`](#conversationsselect-select-menu-with-conversations-list)
- [`<ChannelsSelect>`](#channelsselect-select-menu-with-channel-list)

In jsx-slack, [`<Input>` component also can use as the block element for plain text input in modal.](#input-element)
- [`<DatePicker>`](#datepicker-select-date-from-calendar)

## Block elements

Expand Down Expand Up @@ -319,7 +318,9 @@ A menu element with static options passed by `<Option>` or `<Optgroup>`. It has

[<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?blocks=%5B%7B%22type%22%3A%22actions%22%2C%22elements%22%3A%5B%7B%22type%22%3A%22static_select%22%2C%22placeholder%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22Rate%20it!%22%2C%22emoji%22%3Atrue%7D%2C%22action_id%22%3A%22rating%22%2C%22options%22%3A%5B%7B%22value%22%3A%225%22%2C%22text%22%3A%7B%22text%22%3A%225%20%3Astar%3A%3Astar%3A%3Astar%3A%3Astar%3A%3Astar%3A%22%2C%22type%22%3A%22plain_text%22%2C%22emoji%22%3Atrue%7D%7D%2C%7B%22value%22%3A%224%22%2C%22text%22%3A%7B%22text%22%3A%224%20%3Astar%3A%3Astar%3A%3Astar%3A%3Astar%3A%22%2C%22type%22%3A%22plain_text%22%2C%22emoji%22%3Atrue%7D%7D%2C%7B%22value%22%3A%223%22%2C%22text%22%3A%7B%22text%22%3A%223%20%3Astar%3A%3Astar%3A%3Astar%3A%22%2C%22type%22%3A%22plain_text%22%2C%22emoji%22%3Atrue%7D%7D%2C%7B%22value%22%3A%222%22%2C%22text%22%3A%7B%22text%22%3A%222%20%3Astar%3A%3Astar%3A%22%2C%22type%22%3A%22plain_text%22%2C%22emoji%22%3Atrue%7D%7D%2C%7B%22value%22%3A%221%22%2C%22text%22%3A%7B%22text%22%3A%221%20%3Astar%3A%22%2C%22type%22%3A%22plain_text%22%2C%22emoji%22%3Atrue%7D%7D%5D%7D%5D%7D%5D)

By defining `multiple` attribute, you also can provide [the selectable menu from multiple options][multi-select] with an appearance is similar to button or text input. The same goes for other menu-like components.
#### Multiple select

By defining `multiple` attribute, you also can provide [the selectable menu from multiple options][multi-select] with an appearance is similar to button or text input. The same goes for other select-like components.

[multi-select]: https://api.slack.com/reference/block-kit/block-elements#multi_select

Expand All @@ -346,9 +347,46 @@ By defining `multiple` attribute, you also can provide [the selectable menu from

> :warning: Slack does not allow to place the multi-select menu in `Actions` block. So jsx-slack throws error if you're trying to use `multiple` attribute in the children of `<Actions>`.

#### Usage in `<Modal>` container

In `<Modal>` container, select-like components may place as children of `<Modal>` directly by wrapping in [`<Input>` layout block](#input-block) by passing `label` prop. Thereby it would allow natural templating like as HTML form style.

```jsx
<Modal title="My App">
<Select
label="Language"
name="language"
title="Pick language you want to learn."
required
>
<Option value="javascript">JavaScript</Option>
<Option value="python">Python</Option>
<Option value="java">Java</Option>
<Option value="c-sharp">C#</Option>
<Option value="php">PHP</Option>
</Select>
</Modal>
```

The above JSX means exactly same as following:

<!-- prettier-ignore-start -->

```jsx
<Modal title="My App">
<Input label="Language" title="Pick language you want to learn." required>
<Select actionId="language">
...
</Select>
</Input>
</Modal>
```

<!-- prettier-ignore-end -->

#### Props

- `actionId` (optional): An identifier for the action.
- `name` / `actionId` (optional): An identifier for the action.
- `placeholder` (optional): A plain text to be shown at first.
- `value` (optional): A value of item to show initially. It must choose value from defined `<Option>` elements in children. It can pass multiple string values by array when `multiple` is enabled.
- `confirm` (optional): [`<Confirm>` element](#confirm-confirmation-dialog) to show confirmation dialog.
Expand All @@ -358,6 +396,13 @@ By defining `multiple` attribute, you also can provide [the selectable menu from
- `multiple` (optional): A boolean value that shows whether multiple options can be selected.
- `maxSelectedItems` (optional): A maximum number of items to allow selected.

##### Props for modal's input

- `label` (**required**): The label string for the element.
- `id` / `blockId` (optional): A string of unique identifier of [`<Input>` layout block](#input-block).
- `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.

#### `<Option>`: Menu item

##### Props
Expand Down Expand Up @@ -412,7 +457,7 @@ It requires setup JSON entry URL in your Slack app. [Learn about external source

#### Props

- `actionId` (optional): An identifier for the action.
- `name` / `actionId` (optional): An identifier for the action.
- `placeholder` (optional): A plain text to be shown at first.
- `initialOption` (optional): An initial option exactly matched to provided options from external source. It allows raw JSON object or `<Option>`. It can pass multiple options by array when `multiple` is enabled.
- `minQueryLength` (optional): A length of typed characters to begin JSON request.
Expand All @@ -423,6 +468,13 @@ It requires setup JSON entry URL in your Slack app. [Learn about external source
- `multiple` (optional): A boolean value that shows whether multiple options can be selected.
- `maxSelectedItems` (optional): A maximum number of items to allow selected.

##### Props for modal's input

- `label` (**required**): The label string for the element.
- `id` / `blockId` (optional): A string of unique identifier of [`<Input>` layout block](#input-block).
- `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.

#### `<SelectFragment>`: Generate options for external source

You would want to build not only the message but also the data source by jsx-slack. `<SelectFragment>` component can create JSON object for external data source usable in `<ExternalSelect>`.
Expand Down Expand Up @@ -454,7 +506,7 @@ A select menu with options consisted of users in the current workspace.

#### Props

- `actionId` (optional): An identifier for the action.
- `name` / `actionId` (optional): An identifier for the action.
- `placeholder` (optional): A plain text to be shown at first.
- `initialUser` (optional): The initial user ID. It can pass multiple string values by array when `multiple` is enabled.
- `confirm` (optional): [`<Confirm>` element](#confirm-confirmation-dialog) to show confirmation dialog.
Expand All @@ -464,13 +516,20 @@ A select menu with options consisted of users in the current workspace.
- `multiple` (optional): A boolean value that shows whether multiple options can be selected.
- `maxSelectedItems` (optional): A maximum number of items to allow selected.

##### Props for modal's input

- `label` (**required**): The label string for the element.
- `id` / `blockId` (optional): A string of unique identifier of [`<Input>` layout block](#input-block).
- `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.

### [`<ConversationsSelect>`: Select menu with conversations list](https://api.slack.com/reference/messaging/block-elements#conversation-select)

A select menu with options consisted of any type of conversations in the current workspace.

#### Props

- `actionId` (optional): An identifier for the action.
- `name` / `actionId` (optional): An identifier for the action.
- `placeholder` (optional): A plain text to be shown at first.
- `initialConversation` (optional): The initial conversation ID. It can pass multiple string values by array when `multiple` is enabled.
- `confirm` (optional): [`<Confirm>` element](#confirm-confirmation-dialog) to show confirmation dialog.
Expand All @@ -480,13 +539,20 @@ A select menu with options consisted of any type of conversations in the current
- `multiple` (optional): A boolean value that shows whether multiple options can be selected.
- `maxSelectedItems` (optional): A maximum number of items to allow selected.

##### Props for modal's input

- `label` (**required**): The label string for the element.
- `id` / `blockId` (optional): A string of unique identifier of [`<Input>` layout block](#input-block).
- `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.

### [`<ChannelsSelect>`: Select menu with channel list](https://api.slack.com/reference/messaging/block-elements#channel-select)

A select menu with options consisted of public channels in the current workspace.

#### Props

- `actionId` (optional): An identifier for the action.
- `name` / `actionId` (optional): An identifier for the action.
- `placeholder` (optional): A plain text to be shown at first.
- `initialChannel` (optional): The initial channel ID. It can pass multiple string values by array when `multiple` is enabled.
- `confirm` (optional): [`<Confirm>` element](#confirm-confirmation-dialog) to show confirmation dialog.
Expand All @@ -496,6 +562,13 @@ A select menu with options consisted of public channels in the current workspace
- `multiple` (optional): A boolean value that shows whether multiple options can be selected.
- `maxSelectedItems` (optional): A maximum number of items to allow selected.

##### Props for modal's input

- `label` (**required**): The label string for the element.
- `id` / `blockId` (optional): A string of unique identifier of [`<Input>` layout block](#input-block).
- `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.

### [`<Overflow>`: Overflow menu](https://api.slack.com/reference/messaging/block-elements#overflow)

An overflow menu displayed as `...` can access to some hidden menu items by many actions. _It must contain least of 2 `<OverflowItem>` components._
Expand Down Expand Up @@ -542,11 +615,28 @@ An easy way to let the user selecting any date is using `<DatePicker>` component

#### Props

- `actionId` (optional): An identifier for the action.
- `name` / `actionId` (optional): An identifier for the action.
- `placeholder` (optional): A plain text to be shown at first.
- `initialDate` (optional): An initially selected date. It allows `YYYY-MM-DD` formatted string, UNIX timestamp in millisecond, and JavaScript [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) instance.
- `confirm` (optional): [`<Confirm>` element](#confirm-confirmation-dialog) to show confirmation dialog.

##### Props for modal's input

As same as [select-like elements](#usage-in-modal-container), `<DatePicker>` also can place as children of `<Modal>` by passing required props.

```jsx
<Modal title="My App">
<DatePicker label="Date" name="date" />
</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?blocks=%5B%7B%22type%22%3A%22input%22%2C%22label%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22Date%22%2C%22emoji%22%3Atrue%7D%2C%22optional%22%3Atrue%2C%22element%22%3A%7B%22type%22%3A%22datepicker%22%2C%22action_id%22%3A%22date%22%7D%7D%5D&mode=modal)

- `label` (**required**): The label string for the element.
- `id` / `blockId` (optional): A string of unique identifier of [`<Input>` layout block](#input-block).
- `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.

### [`<Input>`: Plain-text input element](https://api.slack.com/reference/block-kit/block-elements#input) <a name="input-element" id="input-element">(Only for modal)</a>

`<Input>` block element is for placing a single-line input form within `<Modal>`. It has a interface similar to `<input>` HTML element.
Expand Down
4 changes: 2 additions & 2 deletions src/block-kit/Actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { BlockComponentProps } from './Blocks'
import { ButtonProps } from './elements/Button'
import { SingleSelectPropsBase } from './elements/Select'
import { OverflowProps } from './elements/Overflow'
import { DatePickerProps } from './elements/DatePicker'
import { DatePickerBaseProps } from './elements/DatePicker'

interface ActionsProps extends BlockComponentProps {
children: JSXSlack.Children<
ButtonProps | SingleSelectPropsBase | OverflowProps | DatePickerProps
ButtonProps | SingleSelectPropsBase | OverflowProps | DatePickerBaseProps
>
}

Expand Down
38 changes: 26 additions & 12 deletions src/block-kit/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { BlockComponentProps } from './Blocks'
import { plainText } from './composition/utils'
import { PlainTextInput } from './elements/PlainTextInput'

interface InputCommonProps extends BlockComponentProps {
export interface InputCommonProps extends BlockComponentProps {
hint?: string
title?: string
label: string
required?: boolean
}
Expand All @@ -16,7 +17,6 @@ interface InputBlockProps extends InputCommonProps {
children: JSXSlack.Node<{}>

// Disallow defining attributes for component usage
title?: undefined
actionId?: undefined
name?: undefined
placeholder?: undefined
Expand All @@ -28,7 +28,6 @@ interface InputBlockProps extends InputCommonProps {
interface InputComponentProps extends InputCommonProps {
children?: undefined

title?: string // => InputBlockProps.hint (Alias)
actionId?: string // => PlainTextInput.actionId
name?: string // => PlainTextInput.actionId (Alias)
placeholder?: string // => PlainTextInput.placeholder
Expand All @@ -40,35 +39,50 @@ interface InputComponentProps extends InputCommonProps {
type InputProps = InputBlockProps | InputComponentProps
type TextareaProps = InputComponentProps

const InputComponent: JSXSlack.FC<
InputComponentProps & { multiline?: boolean }
> = props => (
export type WithInputProps<T> =
| T & { [key in keyof InputCommonProps]?: undefined }
| T & InputCommonProps

export const wrapInInput = (
element: JSXSlack.Node<any>,
props: InputCommonProps
) => (
<Input
blockId={props.blockId}
hint={props.hint || props.title}
children={element}
hint={props.hint}
id={props.id}
label={props.label}
required={props.required}
>
title={props.title}
/>
)

const InputComponent: JSXSlack.FC<
InputComponentProps & { multiline?: boolean }
> = props =>
wrapInInput(
<PlainTextInput
actionId={props.actionId || props.name}
initialValue={props.value}
maxLength={coerceToInteger(props.maxLength)}
minLength={coerceToInteger(props.minLength)}
multiline={props.multiline}
placeholder={props.placeholder}
/>
</Input>
)
/>,
props
)

export const Input: JSXSlack.FC<InputProps> = props => {
if (props.children === undefined) return InputComponent(props)

const hintText = props.hint || props.title

return (
<ObjectOutput<InputBlock>
type="input"
block_id={props.id || props.blockId}
hint={props.hint ? plainText(props.hint) : undefined}
hint={hintText ? plainText(hintText) : undefined}
label={plainText(props.label)}
optional={!props.required}
element={JSXSlack(props.children)}
Expand Down
36 changes: 22 additions & 14 deletions src/block-kit/elements/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,40 @@ import { JSXSlack } from '../../jsx'
import { ObjectOutput } from '../../utils'
import { ConfirmProps } from '../composition/Confirm'
import { plainText } from '../composition/utils'
import { WithInputProps, wrapInInput } from '../Input'

export interface DatePickerProps {
export interface DatePickerBaseProps {
actionId?: string
children?: undefined
confirm?: JSXSlack.Node<ConfirmProps>
initialDate?: string | Date
name?: string
placeholder?: string
}

type DatePickerProps = WithInputProps<DatePickerBaseProps>

const formatYMD = (date: Date) =>
[
`${date.getFullYear()}`.padStart(4, '0'),
`${date.getMonth() + 1}`.padStart(2, '0'),
`${date.getDate()}`.padStart(2, '0'),
].join('-')

export const DatePicker: JSXSlack.FC<DatePickerProps> = props => (
<ObjectOutput<Datepicker>
type="datepicker"
action_id={props.actionId}
confirm={props.confirm ? JSXSlack(props.confirm) : undefined}
placeholder={props.placeholder ? plainText(props.placeholder) : undefined}
initial_date={
props.initialDate instanceof Date
? formatYMD(props.initialDate)
: props.initialDate
}
/>
)
export const DatePicker: JSXSlack.FC<DatePickerProps> = props => {
const element = (
<ObjectOutput<Datepicker>
type="datepicker"
action_id={props.actionId || props.name}
confirm={props.confirm ? JSXSlack(props.confirm) : undefined}
placeholder={props.placeholder ? plainText(props.placeholder) : undefined}
initial_date={
props.initialDate instanceof Date
? formatYMD(props.initialDate)
: props.initialDate
}
/>
)

return props.label ? wrapInInput(element, props) : element
}
Loading