Skip to content

Commit

Permalink
✨ Improve accordion options to allow hide/show expand button
Browse files Browse the repository at this point in the history
  • Loading branch information
tanguyantoine committed Nov 30, 2023
1 parent bdb89d7 commit 4f6fd3c
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 9 deletions.
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
24 changes: 15 additions & 9 deletions src/components/accordion-ui-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface AccordionActionIdParams {
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;
export interface AccordionUIComponentParams<T> {
items: T[];
paginator: AccordionStateManager;
Expand All @@ -26,6 +26,7 @@ export interface AccordionUIComponentParams<T> {
titleTextFunction: AccordionTitleTextFn<T>;
actionIdFunction: AccordionActionIdFn;
builderFunction: AccordionBuilderFn<T>;
isExpandableFunction: AccordionIsExpandableFn<T>;
}

export class AccordionUIComponent<T> {
Expand All @@ -43,6 +44,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 +54,23 @@ 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)) {
section.accessory(Elements.Button({
text: isExpanded ? this.collapseButtonText : this.expandButtonText,
actionId: this.actionIdFunction({
expandedItems: this.paginator.getNextStateByItemIndex(index),
}),
}));
}
const blocks = [
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',
}));
});
});

0 comments on commit 4f6fd3c

Please sign in to comment.