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

✨ Improve accordion options to allow hide/show expand button #127

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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ The `Accordion` component supports optional customizations, such as:
`expandButtonText` – Used to pass in custom text for the button that expands an item, but has a default.

`collapseButtonText` – Used to pass in custom text for the button that collapses an expanded item, but has a default.

`isExpandable` – Used to display or not the expand/collapse button for a given item.

```javascript
import { Modal, Blocks, Accordion } from 'slack-block-builder';
Expand Down
4 changes: 4 additions & 0 deletions docs/components/accordion.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ Used to customize the text for the expand button, which defaults to `'More'`.

Used to customize the text for the collapse button, which defaults to `'Close'`.

`isExpandable` – *Function* `Optional`

Used to show/hide expand/collapse button for a given item.

### The `titleText` Function

The `titleText` parameter accepts a function that takes an object that contains one of the items from the data set and returns a string to display as the title, next to the collapse/expand button. The object, at the moment, contains only one parameter, `item`, which is the item for which the title will be displayed.
Expand Down
23 changes: 16 additions & 7 deletions src/components/accordion-ui-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type AccordionActionIdFn = StringReturnableFn<AccordionActionIdParams>;
export type AccordionTitleTextFn<T> = StringReturnableFn<{ item: T }>;
export type AccordionBuilderFn<T> = BlockBuilderReturnableFn<{ item: T }>;

export type AccordionIsExpandableFn<T> = (item: T) => boolean;
tanguyantoine marked this conversation as resolved.
Show resolved Hide resolved
export interface AccordionUIComponentParams<T> {
items: T[];
paginator: AccordionStateManager;
Expand All @@ -26,6 +27,7 @@ export interface AccordionUIComponentParams<T> {
titleTextFunction: AccordionTitleTextFn<T>;
actionIdFunction: AccordionActionIdFn;
builderFunction: AccordionBuilderFn<T>;
isExpandableFunction: AccordionIsExpandableFn<T>;
}

export class AccordionUIComponent<T> {
Expand All @@ -43,6 +45,8 @@ export class AccordionUIComponent<T> {

private readonly builderFunction: AccordionBuilderFn<T>;

private readonly isExpandableFunction: AccordionIsExpandableFn<T>;

constructor(params: AccordionUIComponentParams<T>) {
this.items = params.items;
this.paginator = params.paginator;
Expand All @@ -51,20 +55,25 @@ export class AccordionUIComponent<T> {
this.titleTextFunction = params.titleTextFunction;
this.actionIdFunction = params.actionIdFunction;
this.builderFunction = params.builderFunction;
this.isExpandableFunction = params.isExpandableFunction;
}

public getBlocks(): BlockBuilder[] {
const unpruned = this.items.map((item, index) => {
const isExpanded = this.paginator.checkItemIsExpandedByIndex(index);
const section = Blocks.Section({ text: this.titleTextFunction({ item }) });

if (this.isExpandableFunction(item)) {
tanguyantoine marked this conversation as resolved.
Show resolved Hide resolved
section.accessory(Elements.Button({
text: isExpanded ? this.collapseButtonText : this.expandButtonText,
actionId: this.actionIdFunction({
expandedItems: this.paginator.getNextStateByItemIndex(index),
}),
}));
}

const blocks = [
tanguyantoine marked this conversation as resolved.
Show resolved Hide resolved
Blocks.Section({ text: this.titleTextFunction({ item }) })
.accessory(Elements.Button({
text: isExpanded ? this.collapseButtonText : this.expandButtonText,
actionId: this.actionIdFunction({
expandedItems: this.paginator.getNextStateByItemIndex(index),
}),
})),
section,
...isExpanded ? this.builderFunction({ item }).flat() : [],
];

Expand Down
4 changes: 4 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
AccordionTitleTextFn,
AccordionActionIdFn,
AccordionBuilderFn,
AccordionIsExpandableFn,
} from './accordion-ui-component';
import {
PaginatorStateManager,
Expand Down Expand Up @@ -102,6 +103,7 @@ interface AccordionBaseParams<T> {
titleText: AccordionTitleTextFn<T>;
actionId: AccordionActionIdFn;
blocksForExpanded: AccordionBuilderFn<T>,
isExpandable?: AccordionIsExpandableFn<T>,
}

export type AccordionParams<T> = AccordionBaseParams<T> & AccordionStateManagerParams;
Expand All @@ -112,6 +114,7 @@ export type AccordionParams<T> = AccordionBaseParams<T> & AccordionStateManagerP
* @param {AccordionTitleTextFn} [params.titleText] A function that receives an object with a single item and returns a string to be displayed next to the expand/collapse button.
* @param {AccordionActionIdFn} [params.actionId] A function that receives the accordion state data and returns a string to set as the action IDs of the expand/collapse buttons.
* @param {AccordionBuilderFn} [params.blocksForExpanded] A function that receives an object with a single item and returns the blocks to create for that item.
* @param {AccordionIsExpandableFn} [params.isExpandable] A function that receives an item and and returns a boolean that tells if the section should have an expand/collapse button.
* @param {string} [params.expandButtonText] The text to display on the button that expands an item in the UI.
* @param {string} [params.collapseButtonText] The text to display on the button that collapses an item in the UI.
*
Expand All @@ -129,6 +132,7 @@ export function Accordion<T>(params: AccordionParams<T>): AccordionUIComponent<T
titleTextFunction: params.titleText,
actionIdFunction: params.actionId,
builderFunction: params.blocksForExpanded,
isExpandableFunction: params.isExpandable || (() => true),
});
}

Expand Down
237 changes: 237 additions & 0 deletions tests/components/accordion.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2435,4 +2435,241 @@ describe('Testing Accordion:', () => {
type: 'modal',
}));
});

test('Check that only expandable items get a expand/collapse button', () => {
const result = Modal({ title: 'Testing' })
.blocks(
Accordion<Human>({
items: humans,
expandedItems: [],
titleText: ({ item }) => `${item.firstName} ${item.lastName}`,
actionId: (params) => JSON.stringify(params),
blocksForExpanded: ({ item: human }) => [
Blocks.Section({ text: `${human.firstName} ${human.lastName}` }),
setIfTruthy(human, [
Blocks.Section({ text: `${human.jobTitle}` }),
Blocks.Section({ text: `${human.department}` }),
]),
Blocks.Section({ text: `${human.email}` }),
],
isExpandable: (item) => item.firstName === 'Taras',
}).getBlocks(),
)
.buildToJSON();

expect(result).toEqual(JSON.stringify({
title: {
type: 'plain_text',
text: 'Testing',
},
blocks: [
{
text: {
type: 'mrkdwn',
text: 'Ray East',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Taras Neporozhniy',
},
accessory: {
text: {
type: 'plain_text',
text: 'More',
},
action_id: '{"expandedItems":[1]}',
type: 'button',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Dima Tereshuk',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Lesha Power',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Yozhef Hisem',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Andrey Roland',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Vlad Filimonov',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Boris Boriska',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Vadim Grabovyy',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Alex Chernyshov',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Serega Grigoruk',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Igor Roik',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Dima Tretiakov',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Sasha Chernyavska',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Arthur Nick',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Dima Lutsik',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Dima Svirepchuk',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Dima Bilkun',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Pasha Akimenko',
},
type: 'section',
},
{
type: 'divider',
},
{
text: {
type: 'mrkdwn',
text: 'Karina Suprun',
},
type: 'section',
},
],
type: 'modal',
}));
});
});
Loading