From 4ebff660534769b8e06947578a64d8c7f570f6b1 Mon Sep 17 00:00:00 2001 From: Maksim Efremov Date: Wed, 1 Nov 2023 15:46:08 +0300 Subject: [PATCH] feat: make section collapsible --- .../CollapsibleSection.scss | 11 ++++ .../CollapsibleSection/CollapsibleSection.tsx | 51 +++++++++++++++++++ src/dialog/Dialog/Dialog.tsx | 49 +++++++++++------- src/stories/dialog/03_Sections.stories.tsx | 21 +++++++- 4 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 src/dialog/CollapsibleSection/CollapsibleSection.scss create mode 100644 src/dialog/CollapsibleSection/CollapsibleSection.tsx diff --git a/src/dialog/CollapsibleSection/CollapsibleSection.scss b/src/dialog/CollapsibleSection/CollapsibleSection.scss new file mode 100644 index 0000000..ffdf572 --- /dev/null +++ b/src/dialog/CollapsibleSection/CollapsibleSection.scss @@ -0,0 +1,11 @@ +.df-collapsible-section { + overflow: hidden; + + &__caption_collapsible { + cursor: pointer; + } + + &__content_collapsed { + display: none; + } +} diff --git a/src/dialog/CollapsibleSection/CollapsibleSection.tsx b/src/dialog/CollapsibleSection/CollapsibleSection.tsx new file mode 100644 index 0000000..03993da --- /dev/null +++ b/src/dialog/CollapsibleSection/CollapsibleSection.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import {ChevronDown, ChevronUp} from '@gravity-ui/icons'; + +import {dfCN} from '../../helpers/cn'; + +import './CollapsibleSection.scss'; + +const block = dfCN('collapsible-section'); + +export type CollapsibleSectionProps = { + className?: string; + + alwaysExpanded?: boolean; + initialCollapsed?: boolean; + + caption: string; + captionClassName?: string; + + children?: React.ReactNode; +}; + +export function CollapsibleSection({ + className, + alwaysExpanded, + initialCollapsed, + children, + caption, + captionClassName, +}: CollapsibleSectionProps) { + const [collapsed, setCollapsed] = React.useState(initialCollapsed && !alwaysExpanded); + const handleToggle = React.useCallback(() => { + setCollapsed(!collapsed); + }, [collapsed]); + + return ( +
+

+ {caption} + {!alwaysExpanded && {collapsed ? : }} +

+
{children}
+
+ ); +} diff --git a/src/dialog/Dialog/Dialog.tsx b/src/dialog/Dialog/Dialog.tsx index 7bb4ac4..e60bf14 100644 --- a/src/dialog/Dialog/Dialog.tsx +++ b/src/dialog/Dialog/Dialog.tsx @@ -51,6 +51,7 @@ import { TabControlStaticApi, ValidatorType, } from '../types'; +import {CollapsibleSection} from '../CollapsibleSection/CollapsibleSection'; const bDialog = dfCN('dialog'); const bPage = dfCN('page-dialog'); @@ -224,6 +225,9 @@ type SectionType = { fields?: FieldT[] | SeparatorType; wrapTo?: (sectionNode: React.ReactNode) => React.ReactNode; + collapsible?: boolean; + initialCollapsed?: boolean; + type?: never; }; @@ -475,10 +479,6 @@ class Dialog< return ; } - static renderSectionTitle(field: SectionType) { - return

{field.section}

; - } - static renderSeparator(_field: SeparatorType, index: number) { return
; } @@ -750,24 +750,35 @@ class Dialog< } renderSection(field: SectionType, index: number, fieldPrefix?: string) { - let sectionNode: React.ReactNode; + const { + wrapTo = (sectionNode) => sectionNode, + section, + collapsible, + initialCollapsed, + } = field; + let children: React.ReactNode; if (!Array.isArray(field.fields) || field.fields.length === 0) { - sectionNode = Dialog.renderSectionTitle(field); + children = []; } else { - sectionNode = ( -
- {Dialog.renderSectionTitle(field)} - {this.renderFields(field.fields, fieldPrefix)} -
- ); - } - - if (typeof field.wrapTo === 'function') { - sectionNode = field.wrapTo(sectionNode); + children = this.renderFields(field.fields, fieldPrefix); } const key = `section-field-${index}`; - return {sectionNode}; + return ( + + {wrapTo( + + {children} + , + )} + + ); } renderFields(fields: Array>, fieldPrefix?: string) { @@ -1113,9 +1124,9 @@ class Dialog< handleSubmit: () => void; form: FormApi; }): React.ReactNode => { - const {className} = this.props; + const {className, modal} = this.props; return ( -
+
{this.renderDialogContent(handleSubmit, form)}
); diff --git a/src/stories/dialog/03_Sections.stories.tsx b/src/stories/dialog/03_Sections.stories.tsx index ecbca16..f828c67 100644 --- a/src/stories/dialog/03_Sections.stories.tsx +++ b/src/stories/dialog/03_Sections.stories.tsx @@ -86,10 +86,11 @@ function DialogDemo({ caption: 'Last name', }, ], - wrapTo: wrapTo.bind(null, 'lightgray'), + wrapTo: wrapTo.bind(null, undefined), }, { section: 'Contacts', + collapsible: true, fields: [ { name: 'email', @@ -104,6 +105,24 @@ function DialogDemo({ ], wrapTo: wrapTo.bind(null, 'cyan'), }, + { + section: 'Address', + collapsible: true, + initialCollapsed: true, + fields: [ + { + name: 'city', + type: 'text', + caption: 'City', + }, + { + name: 'street', + type: 'text', + caption: 'Street', + }, + ], + wrapTo: wrapTo.bind(null, 'magenta'), + }, { type: 'block', name: 'block',