slack_blocks/
blox.rs

1//! # XML macro builder support
2//!
3//! # Blocks
4//!
5//! [`blocks::Actions`] - `<`[`actions_block`]`>`
6//!
7//! [`blocks::Header`] - `<`[`header_block`]`>` or `<`[`h1`]`>`
8//!
9//! [`blocks::Block::Divider`] - `<`[`divider_block`]`>` or `<`[`hr`]`>`
10//!
11//! [`blocks::Section`] - `<`[`section_block`]`>`
12//!
13//! [`blocks::Input`] - `<`[`input_block`]`>`
14//!
15//! [`blocks::Context`] - `<`[`context_block`]`>`
16//!
17//! [`blocks::File`] - `<`[`file_block`]`>`
18//!
19//! [`blocks::Image`] - `<`[`img_block`]`>`
20//!
21//! # Block Elements
22//!
23//! [`elems::TextInput`] - `<`[`text_input`]`>`
24//!
25//! [`elems::Image`] - `<`[`img`]`>`
26//!
27//! [`elems::Button`] - `<`[`button`]`>`
28//!
29//! [`elems::Checkboxes`] - `<`[`checkboxes`]`>`
30//!
31//! [`elems::DatePicker`] - `<`[`date_picker`]`>`
32//!
33//! [`elems::Overflow`] - `<`[`overflow`]`>`
34//!
35//! [`elems::Radio`] - `<`[`radio_buttons`]`>`
36//!
37//! [`elems::select`] - `<`[`select`]`>`
38//!
39//! # Composition Objects
40//!
41//! [`compose::text`] - `<`[`text()`]`>`
42//!
43//! [`compose::opt`] - `<`[`option`]`>`
44//!
45//! [`compose::opt_group`] - `<`[`option_group`]`>`
46//!
47//! [`compose::Confirm`] - `<`[`confirm`]`>`
48//!
49//! # Example
50//! Using an example from Slack's Documentation:
51//! ```json
52//! {
53//!   "type": "section",
54//!   "text": {
55//!     "text": "*Sally* has requested you set the deadline for the Nano launch project",
56//!     "type": "mrkdwn"
57//!   },
58//!   "accessory": {
59//!     "type": "datepicker",
60//!     "action_id": "datepicker123",
61//!     "initial_date": "1990-04-28",
62//!     "placeholder": {
63//!       "type": "plain_text",
64//!       "text": "Select a date"
65//!     }
66//!   }
67//! }
68//! ```
69//! ```rust
70//! use slack_blocks::blox::*;
71//!
72//! let pick_date = blox! {
73//!   <date_picker action_id="datepicker123"
74//!                placeholder="Select a date"
75//!                initial_date=(28, 4, 1990) />
76//! };
77//!
78//! let section = blox! {
79//!   <section_block accessory=pick_date>
80//!     <text kind=plain>"*Sally* has requested you set the deadline for the Nano launch project"</text>
81//!   </section_block>
82//! };
83//! ```
84
85pub use elems::{button::Style::{Danger as btn_danger,
86                                Primary as btn_primary},
87                select::build::{choose::{multi, single},
88                                data_source::{conversations,
89                                              external,
90                                              public_channels,
91                                              static_,
92                                              users}}};
93pub use mox::mox as blox;
94pub use text::build::kind::{mrkdwn, plain};
95
96use crate::{blocks, compose, elems, text};
97
98/// Identity trait to appease the mox macro
99pub trait IntoChild: Sized {
100  /// Identity function
101  fn into_child(self) -> Self {
102    self
103  }
104}
105
106impl<T> IntoChild for T {}
107
108pub use blox_blocks::*;
109pub use blox_compose::*;
110pub use blox_elems::*;
111
112mod blox_blocks {
113  use super::*;
114
115  /// Dummy builder so [blocks::Block::Divider] can be built with XML macro
116  #[derive(Debug, Copy, Clone)]
117  pub struct DividerBuilder;
118  impl DividerBuilder {
119    /// Constructs [blocks::Block::Divider]
120    pub fn build(self) -> blocks::Block<'static> {
121      blocks::Block::Divider
122    }
123  }
124
125  /// # [`blocks::Actions`] - `<actions_block>`
126  ///
127  /// Build a [`blocks::Actions`]
128  ///
129  /// |Attribute|Type|Optional|Available as child|
130  /// |-|-|-|-|
131  /// |[`element`](blocks::actions::build::ActionsBuilder::element())|`impl Into<`[`blocks::actions::SupportedElement`]`>`|❌|✅|
132  /// |[`block_id`](blocks::actions::build::ActionsBuilder::block_id())|[`String`] or [`&str`]|✅|❌|
133  ///
134  /// ## Example
135  /// ```
136  /// use slack_blocks::{blocks::Actions, blox::*, elems::Button, text};
137  ///
138  /// let xml = blox! {
139  ///   <actions_block>
140  ///     <button action_id="foo">"Foo"</button>
141  ///     <button action_id="bar">"Bar"</button>
142  ///   </actions_block>
143  /// };
144  ///
145  /// let equivalent =
146  ///   Actions::builder().element(Button::builder().action_id("foo")
147  ///                                               .text("Foo")
148  ///                                               .build())
149  ///                     .element(Button::builder().action_id("bar")
150  ///                                               .text("Bar")
151  ///                                               .build())
152  ///                     .build();
153  ///
154  /// assert_eq!(xml, equivalent);
155  /// ```
156  pub fn actions_block(
157    )
158      -> blocks::actions::build::ActionsBuilderInit<'static>
159  {
160    blocks::Actions::builder()
161  }
162
163  /// # [`blocks::Header`] - `<header_block>` or `<h1>`
164  ///
165  /// Build a [`blocks::Header`]
166  ///
167  /// # Attributes
168  /// |Attribute|Type|Optional|Available as child|
169  /// |-|-|-|-|
170  /// |[`text`](blocks::header::build::HeaderBuilder::text())|[`text::Plain`], [`String`], [`&str`]|❌|✅|
171  /// |[`block_id`](blocks::header::build::HeaderBuilder::block_id())|[`String`] or [`&str`]|✅|❌|
172  ///
173  /// ## Example
174  /// ```
175  /// use slack_blocks::{blocks::Header, blox::*};
176  ///
177  /// let xml = blox! {
178  ///   <h1>"Foo"</h1>
179  /// };
180  ///
181  /// let equivalent = Header::builder().text("Foo").build();
182  ///
183  /// assert_eq!(xml, equivalent);
184  /// ```
185  pub fn header_block() -> blocks::header::build::HeaderBuilderInit<'static> {
186    blocks::Header::builder()
187  }
188
189  /// Alias for [`header_block`]
190  pub fn h1() -> blocks::header::build::HeaderBuilderInit<'static> {
191    blocks::Header::builder()
192  }
193
194  /// # [`blocks::Block::Divider`] - `<divider_block />` or `<hr />`
195  ///
196  /// Build a [`blocks::Block::Divider`]
197  ///
198  /// # Attributes
199  /// None
200  ///
201  /// ## Example
202  /// ```
203  /// use slack_blocks::{blocks::Block, blox::*};
204  ///
205  /// let xml = blox! {
206  ///   <hr />
207  /// };
208  ///
209  /// let equivalent = Block::Divider;
210  ///
211  /// assert_eq!(xml, equivalent);
212  /// ```
213  pub fn divider_block() -> DividerBuilder {
214    DividerBuilder
215  }
216
217  /// Alias for [`divider_block`]
218  pub fn hr() -> DividerBuilder {
219    divider_block()
220  }
221
222  /// # [`blocks::Section`] - `<section_block>`
223  ///
224  /// Build a [`blocks::Section`]
225  ///
226  /// # Attributes
227  /// |Attribute|Type|Optional|Available as child|
228  /// |-|-|-|-|
229  /// |[`text`]     | [`text::Plain`], [`text::Mrkdwn`], or [`text::Text`]|❌*|❌|
230  /// |[`field`]    | [`text::Plain`], [`text::Mrkdwn`], or [`text::Text`]|❌*|✅|
231  /// |[`fields`]   | [`IntoIterator`] over [`text::Text`]                |❌*|❌|
232  /// |[`accessory`]| [`elems::BlockElement`]                             |✅ |❌|
233  /// |[`block_id`] | [`String`] or [`&str`]                              |✅ |❌|
234  ///
235  /// &#42; `text`, `field(s)`, or both are required.
236  ///
237  /// [`text`]: blocks::section::build::SectionBuilder::text()
238  /// [`field`]: blocks::section::build::SectionBuilder::field()
239  /// [`fields`]: blocks::section::build::SectionBuilder::fields()
240  /// [`accessory`]: blocks::section::build::SectionBuilder::accessory()
241  /// [`block_id`]: blocks::section::build::SectionBuilder::block_id()
242  ///
243  /// ## Example
244  /// ```
245  /// use slack_blocks::{blocks::Section, blox::*, text};
246  ///
247  /// let section_text = blox! { <text kind=plain>"Foo"</text> };
248  ///
249  /// let xml = blox! {
250  ///   <section_block text=section_text>
251  ///     <text kind=mrkdwn>"Bar"</text>
252  ///   </section_block>
253  /// };
254  ///
255  /// let equivalent = Section::builder().text(text::Plain::from("Foo"))
256  ///                                    .field(text::Mrkdwn::from("Bar"))
257  ///                                    .build();
258  ///
259  /// assert_eq!(xml, equivalent);
260  /// ```
261  pub fn section_block(
262    )
263      -> blocks::section::build::SectionBuilderInit<'static>
264  {
265    blocks::Section::builder()
266  }
267
268  /// # [`blocks::Input`] - `<input_block>`
269  ///
270  /// Build a [`blocks::Input`]
271  ///
272  /// # Attributes
273  /// |Attribute|Type|Optional|Available as child|
274  /// |-|-|-|-|
275  /// |[`label`](blocks::input::build::InputBuilder::label())|[`text::Plain`], [`text::Mrkdwn`], or [`text::Text`] ([`<text>`](super::text()))|❌|❌|
276  /// |[`element`](blocks::input::build::InputBuilder::element())|`impl Into<`[`blocks::input::SupportedElement`]`>`|❌|✅|
277  /// |[`block_id`](blocks::input::build::InputBuilder::block_id())|[`String`] or [`&str`]|✅|❌|
278  /// |[`hint`](blocks::input::build::InputBuilder::hint())|[`text::Plain`] ([`<text>`](super::text())), [`String`], or [`&str`]|✅|❌|
279  /// |[`dispatch_actions`](blocks::input::build::InputBuilder::dispatch_actions())|[`bool`]|✅|❌|
280  /// |[`optional`](blocks::input::build::InputBuilder::optional())|[`bool`]|✅|❌|
281  ///
282  /// ## Example
283  /// ```
284  /// use slack_blocks::{blocks::Input, blox::*, elems::TextInput, text};
285  ///
286  /// let xml = blox! {
287  ///   <input_block label="foo">
288  ///     <text_input action_id="input" />
289  ///   </input_block>
290  /// };
291  ///
292  /// let equivalent =
293  ///   Input::builder().label("foo")
294  ///                   .element(TextInput::builder().action_id("input").build())
295  ///                   .build();
296  ///
297  /// assert_eq!(xml, equivalent);
298  /// ```
299  pub fn input_block() -> blocks::input::build::InputBuilderInit<'static> {
300    blocks::Input::builder()
301  }
302
303  /// # [`blocks::Context`] - `<context_block>`
304  ///
305  /// Build a [`blocks::Context`]
306  ///
307  /// # Attributes
308  /// |Attribute|Type|Optional|Available as child|
309  /// |-|-|-|-|
310  /// |[`element`](blocks::context::build::ContextBuilder::element())|[`text::Text`] ([`<text>`](super::text())) or [`elems::Image`] ([`<img>`](super::img()))|❌|✅|
311  /// |[`block_id`](blocks::context::build::ContextBuilder::block_id())|[`String`] or [`&str`]|✅|❌|
312  ///
313  /// ## Example
314  /// ```
315  /// use slack_blocks::{blocks::Context, blox::*, elems::Image, text};
316  ///
317  /// let xml = blox! {
318  ///   <context_block>
319  ///     <text kind=plain>"Enjoy this picture of bar"</text>
320  ///     <img src="https://foo.com/bar.png" alt="a pic of bar" />
321  ///   </context_block>
322  /// };
323  ///
324  /// let equivalent =
325  ///   Context::builder().element(text::Plain::from("Enjoy this picture of bar"))
326  ///                     .element(Image::builder().src("https://foo.com/bar.png")
327  ///                                              .alt("a pic of bar")
328  ///                                              .build())
329  ///                     .build();
330  ///
331  /// assert_eq!(xml, equivalent);
332  /// ```
333  pub fn context_block(
334    )
335      -> blocks::context::build::ContextBuilderInit<'static>
336  {
337    blocks::Context::builder()
338  }
339
340  /// # [`blocks::File`] - `<file_block>`
341  ///
342  /// Build a [`blocks::File`]
343  ///
344  /// # Attributes
345  /// |Attribute|Type|Optional|Available as child|
346  /// |-|-|-|-|
347  /// |[`external_id`](blocks::file::build::FileBuilder::external_id())|[`String`] or [`&str`]|❌|✅|
348  /// |[`block_id`](blocks::file::build::FileBuilder::block_id())|[`String`] or [`&str`]|✅|❌|
349  ///
350  /// ## Example
351  /// ```
352  /// use slack_blocks::{blocks::File, blox::*};
353  ///
354  /// let xml = blox! {
355  ///   <file_block external_id="foo" />
356  /// };
357  ///
358  /// let equivalent = File::builder().external_id("foo").build();
359  ///
360  /// assert_eq!(xml, equivalent);
361  /// ```
362  pub fn file_block() -> blocks::file::build::FileBuilderInit<'static> {
363    blocks::File::builder()
364  }
365
366  /// # [`blocks::Image`] - `<img_block>`
367  ///
368  /// Build a [`blocks::Image`]
369  ///
370  /// # Attributes
371  /// |Attribute|Type|Optional|Available as child|
372  /// |-|-|-|-|
373  /// |[`src`](blocks::image::build::ImageBuilder::src())|[`String`] or [`&str`]|❌|❌|
374  /// |[`alt`](blocks::image::build::ImageBuilder::alt())|[`String`] or [`&str`]|❌|❌|
375  /// |[`block_id`](blocks::file::build::FileBuilder::block_id())|[`String`] or [`&str`]|✅|❌|
376  ///
377  /// ## Example
378  /// ```
379  /// use slack_blocks::{blocks::Image, blox::*};
380  ///
381  /// let xml = blox! {
382  ///   <img_block src="https://foo.com/bar.png" alt="a pic of bar" />
383  /// };
384  ///
385  /// let equivalent = Image::builder().src("https://foo.com/bar.png")
386  ///                                  .alt("a pic of bar")
387  ///                                  .build();
388  ///
389  /// assert_eq!(xml, equivalent);
390  /// ```
391  pub fn img_block() -> blocks::image::build::ImageBuilderInit<'static> {
392    blocks::Image::builder()
393  }
394}
395
396mod blox_elems {
397  use super::*;
398
399  /// # [`elems::TextInput`] - `<text_input>`
400  ///
401  /// Build a [`elems::TextInput`]
402  ///
403  /// # Attributes
404  /// |Attribute|Type|Optional|Available as child|
405  /// |-|-|-|-|
406  /// |[`action_id`]      | [`String`] or [`&str`]                      |❌|❌|
407  /// |[`action_trigger`] | [`elems::text_input::ActionTrigger`]        |✅|❌|
408  /// |[`placeholder`]    | [`text::Plain`] ([`<text>`](super::text())), [`String`] or [`&str`]     |✅|❌|
409  /// |[`initial_value`]  | [`String`] or [`&str`]                      |✅|❌|
410  /// |[`length`]         | impl [`std::ops::RangeBounds`] over [`u32`] |✅|❌|
411  /// |[`min_length`]     | [`u32`]                                     |✅|❌|
412  /// |[`max_length`]     | [`u32`]                                     |✅|❌|
413  /// |[`multiline`]      | [`bool`]                                    |✅|❌|
414  ///
415  /// [`action_id`]:      elems::text_input::build::TextInputBuilder::action_id()
416  /// [`action_trigger`]: elems::text_input::build::TextInputBuilder::action_trigger()
417  /// [`placeholder`]:    elems::text_input::build::TextInputBuilder::placeholder()
418  /// [`initial_value`]:  elems::text_input::build::TextInputBuilder::initial_value()
419  /// [`length`]:         elems::text_input::build::TextInputBuilder::length()
420  /// [`min_length`]:     elems::text_input::build::TextInputBuilder::min_length()
421  /// [`max_length`]:     elems::text_input::build::TextInputBuilder::max_length()
422  /// [`multiline`]:      elems::text_input::build::TextInputBuilder::multiline()
423  ///
424  /// ## Example
425  /// ```
426  /// use slack_blocks::{blox::*, elems::TextInput};
427  ///
428  /// let xml: TextInput = blox! {
429  ///   <text_input action_id="name_input"
430  ///               multiline=true
431  ///               placeholder="Type your name"
432  ///               length={1..=1000}
433  ///   />
434  /// };
435  ///
436  /// let equiv = TextInput::builder().action_id("name_input")
437  ///                                 .multiline(true)
438  ///                                 .placeholder("Type your name")
439  ///                                 .length(1..=1000)
440  ///                                 .build();
441  ///
442  /// assert_eq!(xml, equiv)
443  /// ```
444  pub fn text_input(
445    )
446      -> elems::text_input::build::TextInputBuilderInit<'static>
447  {
448    elems::TextInput::builder()
449  }
450
451  /// # [`elems::Image`] - `<img />`
452  ///
453  /// Build a [`elems::Image`]
454  ///
455  /// # Attributes
456  /// |Attribute|Type|Optional|Available as child|
457  /// |-|-|-|-|
458  /// |[`src`](elems::image::build::ImageBuilder::src()) | [`String`] or [`&str`] |❌|❌|
459  /// |[`alt`](elems::image::build::ImageBuilder::alt()) | [`String`] or [`&str`] |❌|❌|
460  ///
461  /// ## Example
462  /// ```
463  /// use slack_blocks::{blox::*, elems::Image};
464  ///
465  /// let xml: Image = blox! {
466  ///   <img src="https://foo.com/bar.png" alt="a pic of bar" />
467  /// };
468  ///
469  /// let equiv = Image::builder().src("https://foo.com/bar.png")
470  ///                             .alt("a pic of bar")
471  ///                             .build();
472  ///
473  /// assert_eq!(xml, equiv)
474  /// ```
475  pub fn img() -> elems::image::build::ImageBuilderInit<'static> {
476    elems::Image::builder()
477  }
478
479  /// # [`elems::Button`] - `<button>`
480  ///
481  /// Build a [`elems::Button`]
482  ///
483  /// # Attributes
484  /// |Attribute|Type|Optional|Available as child|
485  /// |-|-|-|-|
486  /// |[`action_id`] | [`String`] or [`&str`]                                              |❌|❌|
487  /// |[`text`]      | [`text::Plain`] ([`<text>`](super::text())), [`String`] or [`&str`] |❌|✅|
488  /// |[`url`]       | [`String`] or [`&str`]                                              |✅|❌|
489  /// |[`value`]     | [`String`] or [`&str`]                                              |✅|❌|
490  /// |[`style`]     | [`elems::button::Style`] ([`btn_primary`] or [`btn_danger`])        |✅|❌|
491  /// |[`confirm`]   | [`compose::Confirm`] ([`<confirm>`](super::confirm()))              |✅|❌|
492  ///
493  /// [`action_id`]: elems::button::build::ButtonBuilder::action_id()
494  /// [`text`]: elems::button::build::ButtonBuilder::text()
495  /// [`url`]: elems::button::build::ButtonBuilder::url()
496  /// [`value`]: elems::button::build::ButtonBuilder::value()
497  /// [`style`]: elems::button::build::ButtonBuilder::style()
498  /// [`confirm`]: elems::button::build::ButtonBuilder::confirm()
499  ///
500  /// ## Example
501  /// ```
502  /// use slack_blocks::{blox::*,
503  ///                    elems::button::{Button, Style}};
504  ///
505  /// let xml: Button = blox! {
506  ///   <button action_id="dangerous" style=btn_danger>"DANGER!"</button>
507  /// };
508  ///
509  /// let equiv = Button::builder().action_id("dangerous")
510  ///                              .text("DANGER!")
511  ///                              .style(Style::Danger)
512  ///                              .build();
513  ///
514  /// assert_eq!(xml, equiv)
515  /// ```
516  pub fn button() -> elems::button::build::ButtonBuilderInit<'static> {
517    elems::Button::builder()
518  }
519
520  /// # [`elems::Checkboxes`] - `<checkboxes>`
521  ///
522  /// Build a [`elems::Checkboxes`]
523  ///
524  /// # Attributes
525  /// |Attribute|Type|Optional|Available as child|
526  /// |-|-|-|-|
527  /// |[`action_id`]       | [`String`] or [`&str`]                                               |❌|❌|
528  /// |[`option`]          | [`compose::Opt`] ([`<option>`](super::option()))*                    |❌ _(or `options`)_|✅|
529  /// |[`options`]         | [`Vec`] of [`compose::Opt`]*                                         |❌ _(or `option`)_|❌|
530  /// |[`initial_options`] | [`Vec`] of [`compose::Opt`]* provided via [`option`] or [`options`]. |✅|❌|
531  /// |[`confirm`]         | [`compose::Confirm`] ([`<confirm>`](super::confirm()))               |✅|❌|
532  ///
533  /// [`action_id`]: elems::checkboxes::build::CheckboxesBuilder::action_id()
534  /// [`option`]: elems::checkboxes::build::CheckboxesBuilder::option()
535  /// [`options`]: elems::checkboxes::build::CheckboxesBuilder::options()
536  /// [`initial_options`]: elems::checkboxes::build::CheckboxesBuilder::initial_options()
537  /// [`confirm`]: elems::checkboxes::build::CheckboxesBuilder::confirm()
538  ///
539  /// &#42; Options cannot have URL set, option text can be mrkdwn or plain.
540  ///
541  /// ## Example
542  /// ```
543  /// use slack_blocks::{blox::*, compose::Opt, elems::Checkboxes};
544  ///
545  /// let xml: Checkboxes = blox! {
546  ///   <checkboxes action_id="chex">
547  ///     <option value="check_1">
548  ///       <text kind=plain>"Foo"</text>
549  ///     </option>
550  ///     <option value="check_2">
551  ///       <text kind=mrkdwn>"Bar"</text>
552  ///     </option>
553  ///     <option value="check_3">
554  ///       <text kind=plain>"Zoo"</text>
555  ///     </option>
556  ///   </checkboxes>
557  /// };
558  ///
559  /// let equiv = Checkboxes::builder().action_id("chex")
560  ///                                  .option(Opt::builder().value("check_1")
561  ///                                                        .text_plain("Foo")
562  ///                                                        .build())
563  ///                                  .option(Opt::builder().value("check_2")
564  ///                                                        .text_md("Bar")
565  ///                                                        .build())
566  ///                                  .option(Opt::builder().value("check_3")
567  ///                                                        .text_plain("Zoo")
568  ///                                                        .build())
569  ///                                  .build();
570  ///
571  /// assert_eq!(xml, equiv)
572  /// ```
573  pub fn checkboxes(
574    )
575      -> elems::checkboxes::build::CheckboxesBuilderInit<'static>
576  {
577    elems::Checkboxes::builder()
578  }
579
580  /// # [`elems::DatePicker`] - `<date_picker>`
581  ///
582  /// Build a [`elems::DatePicker`]
583  ///
584  /// # Attributes
585  /// |Attribute|Type|Optional|Available as child|
586  /// |-|-|-|-|
587  /// |[`action_id`]    | [`String`] or [`&str`]                                 |❌|❌|
588  /// |[`placeholder`]  | [`String`] or [`&str`]                                 |✅|❌|
589  /// |[`initial_date`] | ([`u8`] _day_, [`u8`] _month_, [`u16`] _year_)         |✅|❌|
590  /// |[`confirm`]      | [`compose::Confirm`] ([`<confirm>`](super::confirm())) |✅|❌|
591  ///
592  /// [`action_id`]: elems::date_picker::build::DatePickerBuilder::action_id()
593  /// [`placeholder`]: elems::date_picker::build::DatePickerBuilder::placeholder()
594  /// [`initial_date`]: elems::date_picker::build::DatePickerBuilder::initial_date()
595  /// [`confirm`]: elems::date_picker::build::DatePickerBuilder::confirm()
596  ///
597  /// ## Example
598  /// ```
599  /// use slack_blocks::{blox::*, elems::DatePicker};
600  ///
601  /// let xml = blox! {
602  ///   <date_picker action_id="pick_birthday" placeholder="Pick your birthday!" />
603  /// };
604  ///
605  /// let equiv = DatePicker::builder().action_id("pick_birthday")
606  ///                                  .placeholder("Pick your birthday!")
607  ///                                  .build();
608  ///
609  /// assert_eq!(xml, equiv)
610  /// ```
611  pub fn date_picker(
612    )
613      -> elems::date_picker::build::DatePickerBuilderInit<'static>
614  {
615    elems::DatePicker::builder()
616  }
617
618  /// # [`elems::Overflow`] - `<overflow>`
619  ///
620  /// Build a [`elems::Overflow`]
621  ///
622  /// # Attributes
623  /// |Attribute|Type|Optional|Available as child|
624  /// |-|-|-|-|
625  /// |[`action_id`] | [`String`] or [`&str`]                                 |❌|❌|
626  /// |[`option`]    | [`compose::Opt`] ([`<option>`](super::option()))*      |❌ _(or options)_|✅|
627  /// |[`options`]   | [`Vec`] of [`compose::Opt`]*                           |❌ _(or option)_|❌|
628  /// |[`confirm`]   | [`compose::Confirm`] ([`<confirm>`](super::confirm())) |✅|❌|
629  ///
630  /// [`action_id`]: elems::overflow::build::OverflowBuilder::action_id()
631  /// [`option`]: elems::overflow::build::OverflowBuilder::option()
632  /// [`options`]: elems::overflow::build::OverflowBuilder::options()
633  /// [`confirm`]: elems::overflow::build::OverflowBuilder::confirm()
634  ///
635  /// &#42; Options **can** have URL set, option text must be plain.
636  ///
637  /// ## Example
638  /// ```
639  /// use slack_blocks::{blox::*, compose::Opt, elems::Overflow};
640  ///
641  /// let xml = blox! {
642  ///   <overflow action_id="menu">
643  ///     <option value="foo"><text kind=plain>"Foo"</text></option>
644  ///     <option value="url" text_plain="Open link" url="foo.com" />
645  ///   </overflow>
646  /// };
647  ///
648  /// let equiv = Overflow::builder().action_id("menu")
649  ///                                .option(Opt::builder().value("foo")
650  ///                                                      .text_plain("Foo")
651  ///                                                      .build())
652  ///                                .option(Opt::builder().value("url")
653  ///                                                      .text_plain("Open link")
654  ///                                                      .url("foo.com")
655  ///                                                      .build())
656  ///                                .build();
657  ///
658  /// assert_eq!(xml, equiv)
659  /// ```
660  pub fn overflow() -> elems::overflow::build::OverflowBuilderInit<'static> {
661    elems::Overflow::builder()
662  }
663
664  /// # [`elems::Radio`] - `<radio_buttons>`
665  ///
666  /// Build a [`elems::Radio`]
667  ///
668  /// # Attributes
669  /// |Attribute|Type|Optional|Available as child|
670  /// |-|-|-|-|
671  /// |[`action_id`]      | [`String`] or [`&str`]                                 |❌|❌|
672  /// |[`option`]         | [`compose::Opt`] ([`<option>`](super::option()))*      |❌ _(or options)_|✅|
673  /// |[`options`]        | [`Vec`] of [`compose::Opt`]*                           |❌ _(or option)_|❌|
674  /// |[`initial_option`] | [`compose::Opt`]*                                      |✅|❌|
675  /// |[`confirm`]        | [`compose::Confirm`] ([`<confirm>`](super::confirm())) |✅|❌|
676  ///
677  /// [`action_id`]: elems::radio::build::RadioBuilder::action_id()
678  /// [`option`]: elems::radio::build::RadioBuilder::option()
679  /// [`options`]: elems::radio::build::RadioBuilder::options()
680  /// [`initial_option`]: elems::radio::build::RadioBuilder::initial_option()
681  /// [`confirm`]: elems::radio::build::RadioBuilder::confirm()
682  ///
683  /// &#42; Options **cannot** have URL set, option text can be mrkdwn or plain.
684  ///
685  /// ## Example
686  /// ```
687  /// use slack_blocks::{blocks::Input, blox::*, compose::Opt, elems::Radio};
688  ///
689  /// let xml = blox! {
690  ///   <input_block label="Pick your favorite cheese!">
691  ///     <radio_buttons action_id="cheese_picker">
692  ///       <option value="feta"><text kind=plain>"Feta"</text></option>
693  ///       <option value="gouda"><text kind=plain>"Gouda"</text></option>
694  ///       <option value="cheddar"><text kind=plain>"Cheddar"</text></option>
695  ///     </radio_buttons>
696  ///   </input_block>
697  /// };
698  ///
699  /// let equiv = {
700  ///   let feta = Opt::builder().value("feta").text_plain("Feta").build();
701  ///
702  ///   let gouda = Opt::builder().value("gouda").text_plain("Gouda").build();
703  ///
704  ///   let cheddar = Opt::builder().value("cheddar")
705  ///                               .text_plain("Cheddar")
706  ///                               .build();
707  ///
708  ///   let radio = Radio::builder().action_id("cheese_picker")
709  ///                               .option(feta)
710  ///                               .option(gouda)
711  ///                               .option(cheddar)
712  ///                               .build();
713  ///
714  ///   Input::builder().label("Pick your favorite cheese!")
715  ///                   .element(radio)
716  ///                   .build()
717  /// };
718  ///
719  /// assert_eq!(xml, equiv)
720  /// ```
721  pub fn radio_buttons() -> elems::radio::build::RadioBuilderInit<'static> {
722    elems::Radio::builder()
723  }
724
725  /// # [`elems::select`] - `<select>`
726  ///
727  /// Build a [`elems::select`].
728  ///
729  /// # Attributes
730  ///
731  /// This element behaves like a flow chart, allowing you to construct all
732  /// Single and Multi-selects with a single XML element.
733  ///
734  /// Use the `kind` attribute to distinguish between
735  /// single- and multi-selects.
736  ///
737  /// Use the `choose_from` attribute to distinguish between
738  /// the many sources of options for the select menu.
739  ///
740  /// ## Common
741  /// |Attribute|Type|Optional|Available as child|
742  /// |-|-|-|-|
743  /// |[`kind`]        | [`single`] (default) or [`multi`]                                            |✅|❌|
744  /// |[`choose_from`] | [`users`], [`public_channels`], [`static_`], [`external`], [`conversations`] |❌|❌|
745  /// |`action_id`     | [`String`] or [`&str`]                                                       |❌|❌|
746  /// |`placeholder`   | [`String`] or [`&str`]                                                       |✅|❌|
747  /// |`confirm`       | [`compose::Confirm`] ([`<confirm>`](super::confirm()))                       |✅|❌|
748  /// |**when kind=multi**<br/>`max_selected_items` | [`u32`] |✅|❌|
749  ///
750  /// [`kind`]: elems::select::build::SelectBuilder::kind()
751  /// [`choose_from`]: elems::select::build::SelectBuilder::choose_from()
752  ///
753  /// ## **When `choose_from=users`**
754  /// |Attribute|Type|Optional|Available as child|
755  /// |-|-|-|-|
756  /// |**when kind=single**<br/>[`initial_user`]| [`String`] or [`&str`] |✅|❌|
757  /// |**when kind=multi**<br/>[`initial_users`]| impl [`IntoIterator`] over [`String`] or [`&str`] |✅|❌|
758  ///
759  /// [`initial_user`]: elems::select::user::build::UserBuilder::initial_user()
760  /// [`initial_users`]: elems::select::user::build::UserBuilder::initial_users()
761  ///
762  /// ## **When `choose_from=public_channels`**
763  /// |Attribute|Type|Optional|Available as child|
764  /// |-|-|-|-|
765  /// |**when kind=single**<br/>[`initial_channel`]| [`String`] or [`&str`] |✅|❌|
766  /// |**when kind=multi**<br/>[`initial_channels`]| impl [`IntoIterator`] over [`String`] or [`&str`] |✅|❌|
767  ///
768  /// [`initial_channel`]: elems::select::public_channel::build::PublicChannelBuilder::initial_channel()
769  /// [`initial_channels`]: elems::select::public_channel::build::PublicChannelBuilder::initial_channels()
770  ///
771  /// ## **When `choose_from=external`**
772  /// |Attribute|Type|Optional|Available as child|
773  /// |-|-|-|-|
774  /// |[`min_query_length`] | [`u64`] |✅|❌|
775  /// |**when kind=single**<br/>[`initial_option`]| [`compose::Opt`]* |✅|❌|
776  /// |**when kind=single**<br/>[`initial_option_group`]| [`compose::OptGroup`]* |✅|❌|
777  /// |**when kind=multi**<br/>[`initial_options`]| impl [`IntoIterator`] over [`compose::Opt`]s*  |✅|❌|
778  /// |**when kind=multi**<br/>[`initial_option_groups`]| impl [`IntoIterator`] over [`compose::OptGroup`]s*  |✅|❌|
779  ///
780  /// &#42; Options **cannot** have URL set, option text must be plain.
781  ///
782  /// [`initial_option`]: elems::select::external::build::ExternalBuilder::initial_option()
783  /// [`initial_option_group`]: elems::select::external::build::ExternalBuilder::initial_option_group()
784  /// [`initial_option_groups`]: elems::select::external::build::ExternalBuilder::initial_option_groups()
785  /// [`initial_options`]: elems::select::external::build::ExternalBuilder::initial_options()
786  /// [`min_query_length`]: elems::select::external::build::ExternalBuilder::min_query_length()
787  ///
788  /// ## **When `choose_from=conversations`**
789  /// |Attribute|Type|Optional|Available as child|
790  /// |-|-|-|-|
791  /// |[`filter`] | [`compose::ConversationFilter`] |✅|❌|
792  /// |[`initial_channel_current`] | [`bool`] |✅|❌|
793  /// |**when kind=single**<br/>[`initial_channel`]| [`String`] or [`&str`] |✅|❌|
794  /// |**when kind=multi**<br/>[`initial_channels`]| impl [`IntoIterator`] over [`String`]s or [`&str`]s |✅|❌|
795  ///
796  /// [`filter`]: elems::select::conversation::build::ConversationBuilder::filter()
797  /// [`initial_channel_current`]: elems::select::conversation::build::ConversationBuilder::initial_channel_current()
798  /// [`initial_channel`]: elems::select::conversation::build::ConversationBuilder::initial_channel()
799  /// [`initial_channels`]: elems::select::conversation::build::ConversationBuilder::initial_channels()
800  ///
801  /// ## **When `choose_from=static_`**
802  /// This is the most complex select menu.
803  ///
804  /// It allows you to define the options in your app
805  /// by using children or `options`/`option_groups`.
806  ///
807  /// Note that all children must **either** be `<option>`s or `<option_group>`s.
808  /// You cannot mix options and groups in the same static select menu.
809  ///
810  /// For single-selects that use options, you can pre-select one of them with `initial_option`.
811  ///
812  /// For multi-selects that use options, you can pre-select any number of them with `initial_options`.
813  ///
814  /// For single-selects that use option groups, you can pre-select one of them with `initial_option_group`.
815  ///
816  /// For multi-selects that use option groups, you can pre-select any number of them with `initial_option_groups`.
817  ///
818  /// |Attribute|Type|Optional|Available as child|
819  /// |-|-|-|-|
820  /// | Child | [`compose::Opt`] ([`<option>`](super::option())) or [`compose::OptGroup`] ([`<option_group>`](super::option_group()))* |❌ _(or `options` or `option_groups`)_|✅|
821  /// |[`option_groups`] | [`Vec`] of [`compose::OptGroup`]*                                                                           |❌ _(or Child or `option_groups`)_|❌|
822  /// |[`options`]       | [`Vec`] of [`compose::Opt`]*                                                                                |❌ _(or Child or `options`)_ |❌|
823  /// |**when kind=single**<br/>**and select uses Opts**<br/>(not OptGroups)<br/>[`initial_option`]| [`compose::Opt`]* |✅|❌|
824  /// |**when kind=single**<br/>**and select uses OptGroup**<br/>(not Opts)<br/>[`initial_option_group`]| [`compose::OptGroup`]* |✅|❌|
825  /// |**when kind=multi**<br/>**and select uses Opt**<br/>(not OptGroups)<br/>[`initial_options`]| impl [`IntoIterator`] over [`compose::Opt`]* |✅|❌|
826  /// |**when kind=multi**<br/>**and select uses OptGroup**<br/>(not Opts)<br/>[`initial_option_groups`]| impl [`IntoIterator`] over [`compose::OptGroup`]* |✅|❌|
827  ///
828  /// &#42; All children must either be options or option groups. Options **cannot** have URL set, option text must be plain.
829  ///
830  /// [`initial_option`]: elems::select::static_::build::StaticBuilder::initial_option()
831  /// [`initial_options`]: elems::select::static_::build::StaticBuilder::initial_options()
832  /// [`initial_option_group`]: elems::select::static_::build::StaticBuilder::initial_option_group()
833  /// [`initial_option_groups`]: elems::select::static_::build::StaticBuilder::initial_option_groups()
834  /// [`options`]: elems::select::static_::build::StaticBuilder::options()
835  /// [`option_groups`]: elems::select::static_::build::StaticBuilder::option_groups()
836  ///
837  /// # Examples
838  ///
839  /// ## `choose_from=users`
840  /// ```
841  /// use slack_blocks::elems::select;
842  /// use slack_blocks::blox::*;
843  ///
844  /// let xml = blox! {
845  ///   <select kind=multi choose_from=users placeholder="Pick some users!" action_id="foo" />
846  /// };
847  ///
848  /// let equiv = select::multi::User::builder().placeholder("Pick some users!").action_id("foo").build();
849  ///
850  /// assert_eq!(xml, equiv)
851  /// ```
852  ///
853  /// ## `choose_from=static_`
854  /// ```
855  /// use slack_blocks::{elems::select, compose::Opt, blox::*};
856  ///
857  /// let xml = blox! {
858  ///   <select choose_from=static_ placeholder="Pick your favorite cheese!" action_id="foo">
859  ///     <option value="gouda"><text kind=plain>"Gouda"</text></option>
860  ///     <option value="feta"><text kind=plain>"Feta"</text></option>
861  ///     <option value="cheddar"><text kind=plain>"Cheddar"</text></option>
862  ///     <option value="fontina"><text kind=plain>"Fontina"</text></option>
863  ///   </select>
864  /// };
865  ///
866  /// let equiv = select::Static::builder().placeholder("Pick your favorite cheese!")
867  ///                                    .action_id("foo")
868  ///                                    .option(Opt::builder().value("gouda").text_plain("Gouda").build())
869  ///                                    .option(Opt::builder().value("feta").text_plain("Feta").build())
870  ///                                    .option(Opt::builder().value("cheddar").text_plain("Cheddar").build())
871  ///                                    .option(Opt::builder().value("fontina").text_plain("Fontina").build())
872  ///                                    .build();
873  ///
874  /// assert_eq!(xml, equiv)
875  /// ```
876  pub fn select() -> elems::select::build::SelectBuilderInit {
877    elems::select::build::SelectBuilderInit::new()
878  }
879}
880
881mod blox_compose {
882  use super::*;
883
884  /// # [`compose::text`] - `<text>`
885  ///
886  /// Build a [`compose::text`]
887  ///
888  /// # Attributes
889  /// |Attribute|Type|Optional|Available as child|
890  /// |-|-|-|-|
891  /// |[`kind`]                              | [`mrkdwn`] or [`plain`] |❌|❌|
892  /// |[`text`]                              | [`&str`] or [`String`]  |❌|✅|
893  /// |**When kind=plain**<br/>[`emoji`]     | [`bool`]                |✅|❌|
894  /// |**When kind=mrkdwn**<br/>[`verbatim`] | [`bool`]                |✅|❌|
895  ///
896  /// [`kind`]: compose::text::build::TextBuilder::kind()
897  /// [`text`]: compose::text::build::TextBuilder::text()
898  /// [`emoji`]: compose::text::build::TextBuilder::emoji()
899  /// [`verbatim`]: compose::text::build::TextBuilder::verbatim()
900  ///
901  /// ## Example
902  /// ```
903  /// use slack_blocks::{blocks::Section, blox::*, text};
904  ///
905  /// let xml = blox! {
906  ///   <text kind=plain>"Foo"</text>
907  /// };
908  ///
909  /// let equivalent = text::Plain::from("Foo");
910  ///
911  /// assert_eq!(xml, equivalent);
912  /// ```
913  pub fn text() -> text::build::TextBuilderInit {
914    text::Text::builder()
915  }
916
917  /// # [`compose::opt`] - `<option>`
918  ///
919  /// Build a [`compose::opt`]
920  ///
921  /// # Attributes
922  /// |Attribute|Type|Optional|Available as child|
923  /// |-|-|-|-|
924  /// |[`value`] | [`&str`] or [`String`]  |❌|❌|
925  /// |[`text`]  | [`text::Plain`] or [`text::Mrkdwn`] ([`<text>`](super::text())) |❌|✅|
926  /// |[`desc`]  | [`&str`], [`String`], or [`text::Plain`] |✅|❌|
927  /// |[`url`]   | [`&str`] or [`String`]  |✅|❌|
928  ///
929  /// [`value`]: compose::opt::build::OptBuilder::value()
930  /// [`text`]: compose::opt::build::OptBuilder::text()
931  /// [`desc`]: compose::opt::build::OptBuilder::desc()
932  /// [`url`]: compose::opt::build::OptBuilder::url()
933  ///
934  /// ## Example
935  /// ```
936  /// use slack_blocks::{blox::*, compose::Opt};
937  ///
938  /// let xml = blox! {
939  ///   <option value="foo">
940  ///     <text kind=plain>"Foo"</text>
941  ///   </option>
942  /// };
943  ///
944  /// let equivalent = Opt::builder().text_plain("Foo").value("foo").build();
945  ///
946  /// assert_eq!(xml, equivalent);
947  /// ```
948  pub fn option() -> compose::opt::build::OptBuilderInit<'static> {
949    compose::Opt::builder()
950  }
951
952  /// # [`compose::opt_group`] - `<option_group>`
953  ///
954  /// Build a [`compose::opt_group`]
955  ///
956  /// # Attributes
957  /// |Attribute|Type|Optional|Available as child|
958  /// |-|-|-|-|
959  /// |[`label`] | [`&str`], [`String`], or [`text::Plain`]      |❌|❌|
960  /// |[`option`] | [`compose::Opt`]                             |❌ _(or options)_|✅|
961  /// |[`options`] | impl [`IntoIterator`] over [`compose::Opt`] |❌ _(or option)_|❌|
962  ///
963  /// [`label`]: compose::opt_group::build::OptGroupBuilder::label()
964  /// [`option`]: compose::opt_group::build::OptGroupBuilder::option()
965  /// [`options`]: compose::opt_group::build::OptGroupBuilder::options()
966  ///
967  /// Supports up to 100 option objects of the same type.
968  ///
969  /// Note that `syn-rsx` (and `mox` by extension)
970  /// do not support using iterables as children.
971  ///
972  /// This means that if you have a `Vec<Opt>`,
973  /// you'll need to pass it to an `options` attribute instead.
974  ///
975  /// See Examples for an example of compile-time child opts, and runtime (Vec) opts.
976  ///
977  /// ## Example - Options known at compile-time
978  /// ```
979  /// use slack_blocks::{blox::*,
980  ///                    compose::{Opt, OptGroup}};
981  ///
982  /// let xml = blox! {
983  ///   <option_group label="foos_and_bars">
984  ///     <option value="foo"><text kind=plain>"Foo"</text></option>
985  ///     <option value="bar"><text kind=plain>"Bar"</text></option>
986  ///   </option_group>
987  /// };
988  ///
989  /// let equivalent = OptGroup::builder().label("foos_and_bars")
990  ///                                     .option(Opt::builder().value("foo")
991  ///                                                           .text_plain("Foo")
992  ///                                                           .build())
993  ///                                     .option(Opt::builder().value("bar")
994  ///                                                           .text_plain("Bar")
995  ///                                                           .build())
996  ///                                     .build();
997  ///
998  /// assert_eq!(xml, equivalent);
999  /// ```
1000  ///
1001  /// ## Example - Dynamic vec of options
1002  /// ```
1003  /// use slack_blocks::{blox::*,
1004  ///                    compose::{Opt, OptGroup}};
1005  ///
1006  /// # fn uuid() -> String {"foo".to_string()}
1007  /// # fn random_word() -> String {"foo".to_string()}
1008  /// let generate_option = || {
1009  ///   blox! {
1010  ///     <option value={uuid()}>
1011  ///       <text kind=plain>{random_word()}</text>
1012  ///     </option>
1013  ///   }
1014  /// };
1015  ///
1016  /// // Generate 80 random options
1017  /// let options = std::iter::repeat(()).take(80)
1018  ///                                    .map(|_| generate_option())
1019  ///                                    .collect::<Vec<_>>();
1020  ///
1021  /// let xml = blox! {
1022  ///   <option_group label="foos_and_bars" options={options.clone()} />
1023  /// };
1024  ///
1025  /// let equivalent = OptGroup::builder().label("foos_and_bars")
1026  ///                                     .options(options.clone())
1027  ///                                     .build();
1028  ///
1029  /// assert_eq!(xml, equivalent);
1030  /// ```
1031  pub fn option_group(
1032    )
1033      -> compose::opt_group::build::OptGroupBuilderInit<'static>
1034  {
1035    compose::OptGroup::builder()
1036  }
1037
1038  /// # [`compose::Confirm`] - `<confirm>`
1039  ///
1040  /// Build a [`compose::Confirm`]
1041  ///
1042  /// # Attributes
1043  /// |Attribute|Type|Optional|Available as child|
1044  /// |-|-|-|-|
1045  /// |[`title`] | [`&str`], [`String`], or [`text::Plain`]   |❌|❌|
1046  /// |[`text`], [`text_plain`], or [`text_md`]|`text` wants [`text::Plain`] or [`text::Mrkdwn`]. `text_md` & `text_plain` want [`&str`] or [`String`]. |❌|❌|
1047  /// |[`confirm`] | [`&str`], [`String`], or [`text::Plain`] |❌|❌|
1048  /// |[`deny`] | [`&str`], [`String`], or [`text::Plain`]    |❌|❌|
1049  /// |[`style`] | [`compose::confirm::ConfirmStyle`]    |✅|❌|
1050  ///
1051  /// [`title`]: compose::confirm::build::ConfirmBuilder::title()
1052  /// [`text`]: compose::confirm::build::ConfirmBuilder::text()
1053  /// [`text_plain`]: compose::confirm::build::ConfirmBuilder::text_plain()
1054  /// [`text_md`]: compose::confirm::build::ConfirmBuilder::text_md()
1055  /// [`confirm`]: compose::confirm::build::ConfirmBuilder::confirm()
1056  /// [`deny`]: compose::confirm::build::ConfirmBuilder::deny()
1057  /// [`style`]: compose::confirm::build::ConfirmBuilder::style()
1058  ///
1059  /// ## Example
1060  /// ```
1061  /// use slack_blocks::{blox::*, compose::Confirm, text::ToSlackPlaintext};
1062  ///
1063  /// let xml = blox! {
1064  ///   <confirm title="Title"
1065  ///            text="Body".plaintext()
1066  ///            confirm="Yes"
1067  ///            deny="No"
1068  ///   />
1069  /// };
1070  ///
1071  /// let equivalent = Confirm::builder().title("Title")
1072  ///                                    .text("Body".plaintext())
1073  ///                                    .confirm("Yes")
1074  ///                                    .deny("No")
1075  ///                                    .build();
1076  ///
1077  /// assert_eq!(xml, equivalent);
1078  /// ```
1079  pub fn confirm() -> compose::confirm::build::ConfirmBuilderInit {
1080    compose::Confirm::builder()
1081  }
1082}