Skip to content

Commit

Permalink
Merge pull request #63 from speee/input-props-for-select
Browse files Browse the repository at this point in the history
Add input-compatible props to select-like elements and DatePicker
  • Loading branch information
Yuki Hattori authored Oct 1, 2019
2 parents a2b27e8 + bbe3915 commit e204b08
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 80 deletions.
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

0 comments on commit e204b08

Please sign in to comment.