diff --git a/packages/ui-drilldown/src/Drilldown/README.md b/packages/ui-drilldown/src/Drilldown/README.md index 5935a61a66..43abc8f469 100644 --- a/packages/ui-drilldown/src/Drilldown/README.md +++ b/packages/ui-drilldown/src/Drilldown/README.md @@ -166,67 +166,204 @@ type: example Additionally, the `renderLabelInfo` prop can render text or other elements next to the label. -```js ---- -type: example ---- +- ```js + class VideoSettingsExample extends React.Component { + state = { + selectedCaption: 'English', + selectedSpeed: 'Normal', + selectedQuality: '720p', + isCommentsOn: true + } -class VideoSettingsExample extends React.Component { - state = { - selectedCaption: 'English', - selectedSpeed: 'Normal', - selectedQuality: '720p', - isCommentsOn: true - } + renderTrigger() { + return + } - renderTrigger() { - return ( - - ) - } + renderSelected(props, value) { + return ( + + {value} + + ) + } - renderSelected(props, value) { - return ( - - {value} - - ) - } + renderSelectGroup(options, stateName) { + return ( + { + this.setState({ [stateName]: value[0] }) + }} + > + {options.map((option, idx) => ( + { + goToPreviousPage() + }} + > + {option} + + ))} + + ) + } - renderSelectGroup(options, stateName) { - return ( - { - this.setState({ [stateName]: value[0] }) - }} - > - {options.map((option, idx) => ( - { - goToPreviousPage() - }} + render() { + const { captionOptions, speedOptions, qualityOptions } = this.props + + return ( + { + shown && goToPage('videoSettings') + }} + > + - {option} - - ))} - - ) + + this.renderSelected(props, this.state.selectedCaption) + } + > + Captions + + + this.renderSelected(props, this.state.selectedSpeed) + } + > + Speed + + + this.renderSelected(props, this.state.selectedQuality) + } + > + Quality + + { + this.setState((state) => ({ + isCommentsOn: !state.isCommentsOn + })) + }} + role="checkbox" + aria-checked={this.state.isCommentsOn ? 'true' : 'false'} + // prevents reading the label of the checkbox too (duplicated) + aria-describedby={['']} + renderLabelInfo={ + Comments} + variant="toggle" + readOnly + checked={this.state.isCommentsOn} + onChange={() => { + // needed for controlled Checkbox, + // but the state is handled on the Drilldown.Option + }} + labelPlacement="start" + tabIndex={-1} + /> + } + > + Comments + + + + + {this.renderSelectGroup(captionOptions, 'selectedCaption')} + + + + {this.renderSelectGroup(speedOptions, 'selectedSpeed')} + + + + {this.renderSelectGroup(qualityOptions, 'selectedQuality')} + + + ) + } } - render() { - const { - captionOptions, - speedOptions, - qualityOptions - } = this.props + render( + + ) + ``` + +- ```js + const VideoSettingsExample = (props) => { + const [selectedCaption, setSelectedCaption] = useState('English') + const [selectedSpeed, setSelectedSpeed] = useState('Normal') + const [selectedQuality, setSelectedQuality] = useState('720p') + const [isCommentsOn, setIsCommentsOn] = useState(true) + + const renderTrigger = () => { + return + } + + const renderSelected = (props, value) => { + return ( + + {value} + + ) + } + + const renderSelectGroup = (options, selectedState, setSelectedState) => { + return ( + { + setSelectedState(value[0]) + }} + > + {options.map((option, idx) => ( + { + goToPreviousPage() + }} + > + {option} + + ))} + + ) + } return ( { shown && goToPage('videoSettings') }} @@ -247,27 +384,21 @@ class VideoSettingsExample extends React.Component { this.renderSelected(props, this.state.selectedCaption) - } + renderLabelInfo={(props) => renderSelected(props, selectedCaption)} > Captions this.renderSelected(props, this.state.selectedSpeed) - } + renderLabelInfo={(props) => renderSelected(props, selectedSpeed)} > Speed this.renderSelected(props, this.state.selectedQuality) - } + renderLabelInfo={(props) => renderSelected(props, selectedQuality)} > Quality @@ -276,19 +407,18 @@ class VideoSettingsExample extends React.Component { themeOverride={{ padding: '0.25rem 0.75rem' }} afterLabelContentVAlign="center" onOptionClick={() => { - this.setState((state) => ({ isCommentsOn: !state.isCommentsOn })) + setIsCommentsOn((state) => !state) }} role="checkbox" - aria-checked={this.state.isCommentsOn ? 'true' : 'false'} + aria-checked={isCommentsOn ? 'true' : 'false'} // prevents reading the label of the checkbox too (duplicated) aria-describedby={['']} - renderLabelInfo={ Comments} - variant='toggle' + variant="toggle" readOnly - checked={this.state.isCommentsOn} + checked={isCommentsOn} onChange={() => { // needed for controlled Checkbox, // but the state is handled on the Drilldown.Option @@ -302,221 +432,388 @@ class VideoSettingsExample extends React.Component { - - {this.renderSelectGroup(captionOptions, 'selectedCaption')} + + {renderSelectGroup( + props.captionOptions, + selectedCaption, + setSelectedCaption + )} - - {this.renderSelectGroup(speedOptions, 'selectedSpeed')} + + {renderSelectGroup( + props.speedOptions, + selectedSpeed, + setSelectedSpeed + )} - - {this.renderSelectGroup(qualityOptions, 'selectedQuality')} + + {renderSelectGroup( + props.qualityOptions, + selectedQuality, + setSelectedQuality + )} ) } -} -render() -``` + render( + + ) + ``` ### Displaying Drilldown in a Popover Just like [Menu](#Menu), Drilldown accepts a `trigger` property: it will render a toggle button which, when clicked, shows or hides the Drilldown in a [Popover](#Popover). Drilldown passes many of its props to Popover in this case (`mountNode`, `shouldContainFocus`, `withArrow`, etc.). -```js ---- -type: example ---- +- ```js + class SelectContactsExample extends React.Component { + state = { + selected: [] + } -class SelectContactsExample extends React.Component { - state = { - selected: [] - } + getCategoryById(id) { + return this.props.contactsData.find((cat) => cat.id === id) + } - getCategoryById(id) { - return this.props.contactsData.find(cat => cat.id === id) - } + selectContacts(values) { + this.setState({ selected: values }) + } - selectContacts(values) { - this.setState({ selected: values }) - } + renderContacts(contacts) { + return contacts.map((contact, idx) => { + const { id, name, email } = contact - renderContacts(contacts) { - return contacts.map((contact, idx) => { - const { id, name, email } = contact + return ( + + #{idx + 1} | {email} + + } + onOptionClick={() => { + this.selectContacts([contact]) + }} + > + {name} + + ) + }) + } + + renderSubCategoryOptions(subCategories = []) { + return subCategories.map((subCatId, idx) => { + const category = this.getCategoryById(subCatId) + const { id, title } = category + + return ( + + {title} + + ) + }) + } + + renderPage(category, key) { + const { id, title, subCategories = [], options = [] } = category + + const allContacts = this.getAllContactsFromCategory(category) return ( - - #{idx + 1} | {email} - - } - onOptionClick={() => { - this.selectContacts([contact]) + renderTitle={title} + renderActionLabel={`Send to All (${allContacts.length})`} + onHeaderActionClicked={() => { + this.selectContacts(allContacts) }} > - {name} - + {[ + ...this.renderSubCategoryOptions(subCategories), + ...this.renderContacts(options) + ]} + ) - }) - } + } + + getAllContactsFromCategory(category) { + let allContacts = [] - renderSubCategoryOptions(subCategories = []) { - return subCategories.map((subCatId, idx) => { - const category = this.getCategoryById(subCatId) - const { id, title } = category + const addOptions = (cat) => { + const { subCategories = [], options = [] } = cat + allContacts.push(...options) + + subCategories.forEach((subCatId) => { + addOptions(this.getCategoryById(subCatId)) + }) + } + + addOptions(category) + return allContacts + } + + render() { + const { contactsData } = this.props + const { selected = [] } = this.state return ( - - {title} - + + + Select Contacts} + onToggle={(_event, args) => { + args.shown && selectContacts([]) + }} + > + {contactsData.map((page, idx) => this.renderPage(page, idx))} + + + + + +
+ + Selected ({selected ? selected.length : 0}): + +
+
{selected.map((a) => a.name).join(', ')}
+
+
+
) - }) + } } - renderPage(category, key) { - const { id, title, subCategories = [], options = [] } = category + const generateCategory = (name, count) => { + return Array(count) + .fill(name) + .map((category, idx) => { + const id = category.replace(/(-|\s)/g, '').toLowerCase() + return { + id: `${id}${idx + 1}`, + category, + name: `${category} ${idx + 1}`, + email: `${id}${idx + 1}@randommail.com` + } + }) + } - const allContacts = this.getAllContactsFromCategory(category) + const contactsData = [ + { + id: 'contacts', + title: 'Contacts', + subCategories: ['administrators', 'teachers', 'students'] + }, + { + id: 'administrators', + title: 'Administrators', + subCategories: ['accountAdmins', 'subAccountAdmins'] + }, + { + id: 'accountAdmins', + title: 'Account Admins', + options: generateCategory('Account Admin', 8) + }, + { + id: 'subAccountAdmins', + title: 'Sub-Account Admins', + options: generateCategory('Sub-Account Admin', 12) + }, + { + id: 'teachers', + title: 'Teachers', + options: generateCategory('Teacher', 23) + }, + { + id: 'students', + title: 'Students', + options: generateCategory('Student', 34) + } + ] - return ( - { - this.selectContacts(allContacts) - }} - > - {[ - ...this.renderSubCategoryOptions(subCategories), - ...this.renderContacts(options) - ]} - - ) - } + render() + ``` + +- ```js + const SelectContactsExample = (props) => { + const [selected, setSelected] = useState([]) - getAllContactsFromCategory(category) { - let allContacts = [] + const getCategoryById = (id) => { + return props.contactsData.find((cat) => cat.id === id) + } + + const selectContacts = (values) => { + setSelected(values) + } - const addOptions = (cat) => { - const { subCategories = [], options = [] } = cat - allContacts.push(...options) + const renderContacts = (contacts) => { + return contacts.map((contact, idx) => { + const { id, name, email } = contact + + return ( + + #{idx + 1} | {email} + + } + onOptionClick={() => { + selectContacts([contact]) + }} + > + {name} + + ) + }) + } + const renderSubCategoryOptions = (subCategories = []) => { + return subCategories.map((subCatId, idx) => { + const category = getCategoryById(subCatId) + const { id, title } = category - subCategories.forEach((subCatId) => { - addOptions(this.getCategoryById(subCatId)) + return ( + + {title} + + ) }) } - addOptions(category) - return allContacts - } + const renderPage = (category, key) => { + const { id, title, subCategories = [], options = [] } = category + + const allContacts = getAllContactsFromCategory(category) + + return ( + { + selectContacts(allContacts) + }} + > + {[ + ...renderSubCategoryOptions(subCategories), + ...renderContacts(options) + ]} + + ) + } + + const getAllContactsFromCategory = (category) => { + let allContacts = [] + + const addOptions = (cat) => { + const { subCategories = [], options = [] } = cat + allContacts.push(...options) + + subCategories.forEach((subCatId) => { + addOptions(getCategoryById(subCatId)) + }) + } - render() { - const { contactsData } = this.props - const { selected = [] } = this.state + addOptions(category) + return allContacts + } return ( - - + + Select Contacts} onToggle={(_event, args) => { - args.shown && this.selectContacts([]) + args.shown && selectContacts([]) }} > - {contactsData.map((page, idx) => this.renderPage(page, idx))} + {props.contactsData.map((page, idx) => renderPage(page, idx))} - - -
- Selected ({selected ? selected.length : 0}): -
+ +
- {selected.map((a) => a.name).join(', ')} + + Selected ({selected ? selected.length : 0}): +
+
{selected.map((a) => a.name).join(', ')}
) } -} - -const generateCategory = (name, count) => { - return Array(count) - .fill(name) - .map((category, idx) => { - const id = category.replace(/(-|\s)/g, '').toLowerCase() - return { - id: `${id}${idx + 1}`, - category, - name: `${category} ${idx + 1}`, - email: `${id}${idx + 1}@randommail.com` - } - }) -} - -const contactsData = [ - { - id: 'contacts', - title: 'Contacts', - subCategories: ['administrators', 'teachers', 'students'] - }, - { - id: 'administrators', - title: 'Administrators', - subCategories: ['accountAdmins', 'subAccountAdmins'] - }, - { - id: 'accountAdmins', - title: 'Account Admins', - options: generateCategory('Account Admin', 8) - }, - { - id: 'subAccountAdmins', - title: 'Sub-Account Admins', - options: generateCategory('Sub-Account Admin', 12) - }, - { - id: 'teachers', - title: 'Teachers', - options: generateCategory('Teacher', 23) - }, - { - id: 'students', - title: 'Students', - options: generateCategory('Student', 34) + + const generateCategory = (name, count) => { + return Array(count) + .fill(name) + .map((category, idx) => { + const id = category.replace(/(-|\s)/g, '').toLowerCase() + return { + id: `${id}${idx + 1}`, + category, + name: `${category} ${idx + 1}`, + email: `${id}${idx + 1}@randommail.com` + } + }) } -] -render() -``` + const contactsData = [ + { + id: 'contacts', + title: 'Contacts', + subCategories: ['administrators', 'teachers', 'students'] + }, + { + id: 'administrators', + title: 'Administrators', + subCategories: ['accountAdmins', 'subAccountAdmins'] + }, + { + id: 'accountAdmins', + title: 'Account Admins', + options: generateCategory('Account Admin', 8) + }, + { + id: 'subAccountAdmins', + title: 'Sub-Account Admins', + options: generateCategory('Sub-Account Admin', 12) + }, + { + id: 'teachers', + title: 'Teachers', + options: generateCategory('Teacher', 23) + }, + { + id: 'students', + title: 'Students', + options: generateCategory('Student', 34) + } + ] + + render() + ``` ### Page header @@ -538,36 +835,137 @@ The label can be changed with `renderBackButtonLabel` prop (defaults to "Back"). This option will always display on the page when needed and cannot be disabled. -```js ---- -type: example ---- -class BackNavigationExample extends React.Component { - state = { - showTitle: true, - showAction: true, - showAlternativeBackLabel: false +- ```js + class BackNavigationExample extends React.Component { + state = { + showTitle: true, + showAction: true, + showAlternativeBackLabel: false + } + + render() { + const { showTitle, showAction, showAlternativeBackLabel } = this.state + + return ( + + + + + + Option + + + Option + + + Option + + + + + prevPageTitle ? `Back to ${prevPageTitle}` : 'Back' + : undefined + } + > + + Option + + + Option + + + Option + + + + + + + + { + this.setState({ + showAlternativeBackLabel: + !this.state.showAlternativeBackLabel + }) + }} + /> + { + this.setState({ showTitle: !this.state.showTitle }) + }} + /> + { + this.setState({ showAction: !this.state.showAction }) + }} + /> + + + + ) + } } - render() { - const { showTitle, showAction, showAlternativeBackLabel } = this.state + render() + ``` + +- ```js + const BackNavigationExample = () => { + const [showTitle, setShowTitle] = useState(true) + const [showAction, setShowAction] = useState(true) + const [showAlternativeBackLabel, setShowAlternativeBackLabel] = + useState(false) return ( - + - + - + Option - + Option - + Option @@ -576,9 +974,12 @@ class BackNavigationExample extends React.Component { id="secondPage" renderTitle={showTitle && 'Second page'} renderActionLabel={showAction && 'Action!'} - renderBackButtonLabel={showAlternativeBackLabel - ? ((prevPageTitle) => prevPageTitle ? `Back to ${prevPageTitle}` : 'Back') - : undefined} + renderBackButtonLabel={ + showAlternativeBackLabel + ? (prevPageTitle) => + prevPageTitle ? `Back to ${prevPageTitle}` : 'Back' + : undefined + } > Option @@ -596,70 +997,128 @@ class BackNavigationExample extends React.Component { { - this.setState({ showAlternativeBackLabel: !this.state.showAlternativeBackLabel }) - } - } + onChange={() => { + setShowAlternativeBackLabel(!showAlternativeBackLabel) + }} /> { - this.setState({ showTitle: !this.state.showTitle }) - } - } + onChange={() => { + setShowTitle(!showTitle) + }} /> { - this.setState({ showAction: !this.state.showAction }) - } - } + onChange={() => { + setShowAction(!showAction) + }} /> ) } -} -render() -``` + render() + ``` ### Option Groups Wrapping the Options in `` will separate these options with separators. These separators can be hidden, and you can provide a label with the `renderGroupTitle` prop. -```js ---- -type: example ---- -class GroupsExample extends React.Component { - state = { - showSeparators: true, - showTitles: true +- ```js + class GroupsExample extends React.Component { + state = { + showSeparators: true, + showTitles: true + } + + render() { + return ( + + + + + + Milano + Florence + + + + Lyon + Bordeaux + + + + Frankfurt + Dortmund + + + + + + + + { + this.setState({ showSeparators: !this.state.showSeparators }) + }} + /> + { + this.setState({ showTitles: !this.state.showTitles }) + }} + /> + + + + ) + } } - render() { + render() + ``` + +- ```js + const GroupsExample = () => { + const [showSeparators, setShowSeparators] = useState(true) + const [showTitles, setShowTitles] = useState(true) + return ( - + - + Milano Florence @@ -667,8 +1126,8 @@ class GroupsExample extends React.Component { Lyon Bordeaux @@ -676,8 +1135,8 @@ class GroupsExample extends React.Component { Frankfurt Dortmund @@ -689,34 +1148,29 @@ class GroupsExample extends React.Component { { - this.setState({ showSeparators: !this.state.showSeparators }) - } - } + onChange={() => { + setShowSeparators(!showSeparators) + }} /> { - this.setState({ showTitles: !this.state.showTitles }) - } - } + onChange={() => { + setShowTitles(!showTitles) + }} /> ) } -} -render() -``` + render() + ``` #### Selectable Groups @@ -759,80 +1213,263 @@ type: example ##### Multi-select Group -```js ---- -type: example ---- +- ```js + class SelectMembersExample extends React.Component { + state = { + selected: {} + } -class SelectMembersExample extends React.Component { - state = { - selected: {} - } + selectMembers(groupValues) { + this.setState(({ selected }) => { + return { selected: { ...selected, ...groupValues } } + }) + } - selectMembers(groupValues) { - this.setState(({ selected }) => { - return { selected: { ...selected, ...groupValues }} - }) - } + renderGroups(groups) { + return groups.map((group, idx) => { + const { id, label, members, selectableType } = group - renderGroups(groups) { - return groups.map((group, idx) => { - const { id, label, members, selectableType } = group + return ( + { + this.selectMembers({ [id]: value }) + }} + > + {members.map((member, idx) => { + const { id, name } = member + return ( + + {name} + + ) + })} + + ) + }) + } - return ( - { - this.selectMembers({ [id]: value }) - }} - > - {members.map((member, idx) => { - const { id, name } = member - return ( - - {name} - - ) - })} + renderSubPageOptions(subPages = []) { + return subPages.map((subPage, idx) => { + const { id, label } = subPage - + return ( + + {label} + + ) + }) + } + + renderPage(category, key) { + const { id, subPages = [], groups = [] } = category + + return ( + + {subPages.length > 0 && this.renderSubPageOptions(subPages)} + {groups.length > 0 && this.renderGroups(groups)} + ) - }) - } + } - renderSubPageOptions(subPages = []) { - return subPages.map((subPage, idx) => { - const { id, label } = subPage + render() { + const { pages } = this.props + const { selected = {} } = this.state return ( - - {label} - + + + Select Members} + shouldHideOnSelect={false} + onToggle={(_event, { shown }) => { + shown && this.selectMembers({}) + }} + > + {pages.map((page, idx) => this.renderPage(page, idx))} + + + + + +
+ Selected members: +
+
+ + {Object.entries(selected).map(([groupId, values], idx) => { + return values.length > 0 ? ( + + {groupId}: {values.join(', ')} + + ) : null + })} + +
+
+
+
) - }) + } } - renderPage(category, key) { - const { id, subPages = [], groups = [] } = category + const pages = [ + { + id: 'root', + subPages: [ + { id: 'courses', label: 'Courses' }, + { id: 'groups', label: 'Groups' }, + { id: 'consortiums', label: 'Consortiums' } + ] + }, + { + id: 'courses', + groups: [ + { + id: 'course1', + label: 'Course 1', + selectableType: 'multiple', + members: [ + { id: 'course1_1', name: 'Hanna Septimus' }, + { id: 'course1_2', name: 'Kadin Press' }, + { id: 'course1_3', name: 'Kaiya Botosh' } + ] + }, + { + id: 'course2', + label: 'Course 2', + selectableType: 'multiple', + members: [ + { id: 'course2_1', name: 'Leo Calzoni' }, + { id: 'course2_2', name: 'Gretchen Gouse' } + ] + } + ] + }, + { + id: 'groups', + groups: [ + { + id: 'group1', + label: 'Group 1', + selectableType: 'multiple', + members: [ + { id: 'groups1_1', name: 'Penka Okabe' }, + { id: 'groups1_2', name: 'Ausma Meggyesfalvi' }, + { id: 'groups1_3', name: 'Endrit Höfler' }, + { id: 'groups1_4', name: 'Ryuu Carey' }, + { id: 'groups1_5', name: 'Daphne Dioli' } + ] + } + ] + }, + { + id: 'consortiums', + groups: [ + { + id: 'consortium1', + label: 'Consortium 1', + selectableType: 'multiple', + members: [ + { id: 'consortium1_1', name: 'Brahim Gustavsson' }, + { id: 'consortium1_2', name: 'Elodia Dreschner' } + ] + }, + { + id: 'consortium2', + label: 'Consortium 2', + selectableType: 'multiple', + members: [ + { id: 'consortium2_1', name: 'Darwin Peter' }, + { id: 'consortium2_2', name: 'Sukhrab Burnham' } + ] + }, + { + id: 'consortium3', + label: 'Consortium 3', + selectableType: 'multiple', + members: [ + { id: 'consortium3_1', name: 'Jeffry Antonise' }, + { id: 'consortium3_2', name: 'Bia Regenbogen' } + ] + } + ] + } + ] - return ( - - {subPages.length > 0 && this.renderSubPageOptions(subPages)} - {groups.length > 0 && this.renderGroups(groups)} - - ) - } + render() + ``` + +- ```js + const SelectMembersExample = (props) => { + const [selected, setSelected] = useState({}) + + const selectMembers = (groupValues) => { + setSelected((selected) => ({ + ...selected, + ...groupValues + })) + } + + const renderGroups = (groups) => { + return groups.map((group, idx) => { + const { id, label, members, selectableType } = group + + return ( + { + selectMembers({ [id]: value }) + }} + > + {members.map((member, idx) => { + const { id, name } = member + return ( + + {name} + + ) + })} + + ) + }) + } + + const renderSubPageOptions = (subPages = []) => { + return subPages.map((subPage, idx) => { + const { id, label } = subPage + + return ( + + {label} + + ) + }) + } + + const renderPage = (category, key) => { + const { id, subPages = [], groups = [] } = category - render() { - const { pages } = this.props - const { selected = {} } = this.state + return ( + + {subPages.length > 0 && renderSubPageOptions(subPages)} + {groups.length > 0 && renderGroups(groups)} + + ) + } return ( - - + + Select Members} shouldHideOnSelect={false} onToggle={(_event, { shown }) => { - shown && this.selectMembers({}) + shown && selectMembers({}) }} > - {pages.map((page, idx) => this.renderPage(page, idx))} + {props.pages.map((page, idx) => renderPage(page, idx))} - - + +
- Selected members: + Selected members:
- {Object.entries(selected).map(([ groupId, values ], idx) => { + {Object.entries(selected).map(([groupId, values], idx) => { return values.length > 0 ? ( {groupId}: {values.join(', ')} @@ -868,111 +1505,228 @@ class SelectMembersExample extends React.Component { ) } -} - -const pages = [ - { - id: 'root', - subPages: [ - { id: 'courses', label: 'Courses' }, - { id: 'groups', label: 'Groups' }, - { id: 'consortiums', label: 'Consortiums' } - ] - }, - { - id: 'courses', - groups: [ - { - id: 'course1', - label: 'Course 1', - selectableType: 'multiple', - members: [ - { id: 'course1_1', name: 'Hanna Septimus' }, - { id: 'course1_2', name: 'Kadin Press' }, - { id: 'course1_3', name: 'Kaiya Botosh' }, - ] - }, - { - id: 'course2', - label: 'Course 2', - selectableType: 'multiple', - members: [ - { id: 'course2_1', name: 'Leo Calzoni' }, - { id: 'course2_2', name: 'Gretchen Gouse' }, - ] - } - ] - }, - { - id: 'groups', - groups: [ - { - id: 'group1', - label: 'Group 1', - selectableType: 'multiple', - members: [ - { id: 'groups1_1', name: 'Penka Okabe' }, - { id: 'groups1_2', name: 'Ausma Meggyesfalvi' }, - { id: 'groups1_3', name: 'Endrit Höfler' }, - { id: 'groups1_4', name: 'Ryuu Carey' }, - { id: 'groups1_5', name: 'Daphne Dioli' }, - ] - } - ] - }, - { - id: 'consortiums', - groups: [ - { - id: 'consortium1', - label: 'Consortium 1', - selectableType: 'multiple', - members: [ - { id: 'consortium1_1', name: 'Brahim Gustavsson' }, - { id: 'consortium1_2', name: 'Elodia Dreschner' }, - ] - }, - { - id: 'consortium2', - label: 'Consortium 2', - selectableType: 'multiple', - members: [ - { id: 'consortium2_1', name: 'Darwin Peter' }, - { id: 'consortium2_2', name: 'Sukhrab Burnham' }, - ] - }, - { - id: 'consortium3', - label: 'Consortium 3', - selectableType: 'multiple', - members: [ - { id: 'consortium3_1', name: 'Jeffry Antonise' }, - { id: 'consortium3_2', name: 'Bia Regenbogen' }, - ] - } - ] - } -] -render() -``` + const pages = [ + { + id: 'root', + subPages: [ + { id: 'courses', label: 'Courses' }, + { id: 'groups', label: 'Groups' }, + { id: 'consortiums', label: 'Consortiums' } + ] + }, + { + id: 'courses', + groups: [ + { + id: 'course1', + label: 'Course 1', + selectableType: 'multiple', + members: [ + { id: 'course1_1', name: 'Hanna Septimus' }, + { id: 'course1_2', name: 'Kadin Press' }, + { id: 'course1_3', name: 'Kaiya Botosh' } + ] + }, + { + id: 'course2', + label: 'Course 2', + selectableType: 'multiple', + members: [ + { id: 'course2_1', name: 'Leo Calzoni' }, + { id: 'course2_2', name: 'Gretchen Gouse' } + ] + } + ] + }, + { + id: 'groups', + groups: [ + { + id: 'group1', + label: 'Group 1', + selectableType: 'multiple', + members: [ + { id: 'groups1_1', name: 'Penka Okabe' }, + { id: 'groups1_2', name: 'Ausma Meggyesfalvi' }, + { id: 'groups1_3', name: 'Endrit Höfler' }, + { id: 'groups1_4', name: 'Ryuu Carey' }, + { id: 'groups1_5', name: 'Daphne Dioli' } + ] + } + ] + }, + { + id: 'consortiums', + groups: [ + { + id: 'consortium1', + label: 'Consortium 1', + selectableType: 'multiple', + members: [ + { id: 'consortium1_1', name: 'Brahim Gustavsson' }, + { id: 'consortium1_2', name: 'Elodia Dreschner' } + ] + }, + { + id: 'consortium2', + label: 'Consortium 2', + selectableType: 'multiple', + members: [ + { id: 'consortium2_1', name: 'Darwin Peter' }, + { id: 'consortium2_2', name: 'Sukhrab Burnham' } + ] + }, + { + id: 'consortium3', + label: 'Consortium 3', + selectableType: 'multiple', + members: [ + { id: 'consortium3_1', name: 'Jeffry Antonise' }, + { id: 'consortium3_2', name: 'Bia Regenbogen' } + ] + } + ] + } + ] + + render() + ``` ### Disabled prop You can disable the whole Drilldown or its sub-components with the `disabled` prop. The only option that can not be disabled is the Back navigation. -```js ---- -type: example ---- -class DisabledExample extends React.Component { - state = { - disabled: 'None', - withTrigger: false +- ```js + class DisabledExample extends React.Component { + state = { + disabled: 'None', + withTrigger: false + } + + render() { + const { disabled, withTrigger } = this.state + + const disabledDrilldown = disabled === 'Drilldown' + const disabledPages = disabled === 'Pages' + const disabledGroups = disabled === 'Groups' + const disabledOptions = disabled === 'Options' + + return ( + + + + Toggle button} + disabled={disabledDrilldown} + > + + + Option with subPage navigation + + + Option + + + {['Apple', 'Orange', 'Banana', 'Strawberry'].map((item) => ( + + {item} + + ))} + + + + + {['Option 1', 'Option 2', 'Option 3', 'Option 4'].map( + (item) => ( + + {item} + + ) + )} + + + + + + + Settings} + colSpacing="medium" + layout="columns" + vAlign="top" + > + { + this.setState({ disabled: value }) + }} + defaultValue="None" + name="disabledPart" + description="Select disabled" + > + {['None', 'Drilldown', 'Pages', 'Groups', 'Options'].map( + (part) => ( + + ) + )} + + + { + this.setState({ withTrigger: !this.state.withTrigger }) + }} + /> + + + + ) + } } - render() { - const { disabled, withTrigger } = this.state + render() + ``` + +- ```js + const DisabledExample = () => { + const [disabled, setDisabled] = useState('None') + const [withTrigger, setWithTrigger] = useState(false) const disabledDrilldown = disabled === 'Drilldown' const disabledPages = disabled === 'Pages' @@ -980,11 +1734,11 @@ class DisabledExample extends React.Component { const disabledOptions = disabled === 'Options' return ( - - - + + + Toggle button} @@ -998,33 +1752,34 @@ class DisabledExample extends React.Component { > Option with subPage navigation - + Option - {['Apple', 'Orange', 'Banana', 'Strawberry'].map( - item => ( - - {item} - - ) - )} + {['Apple', 'Orange', 'Banana', 'Strawberry'].map((item) => ( + + {item} + + ))} @@ -1034,7 +1789,7 @@ class DisabledExample extends React.Component { disabled={disabledPages} > {['Option 1', 'Option 2', 'Option 3', 'Option 4'].map( - item => ( + (item) => ( { - this.setState({ disabled: value }) + setDisabled(value) }} defaultValue="None" name="disabledPart" description="Select disabled" > - {['None', 'Drilldown', 'Pages', 'Groups', 'Options'].map(part => )} + {['None', 'Drilldown', 'Pages', 'Groups', 'Options'].map( + (part) => ( + + ) + )} { - this.setState({ withTrigger: !this.state.withTrigger }) - } - } + onChange={() => { + setWithTrigger(!withTrigger) + }} /> ) } -} -render() -``` + render() + ``` ### shouldHideOnSelect @@ -1232,179 +1988,400 @@ type: example The following example showcases an editable drilldown that can add or delete options. -```js ---- -type: example ---- +- ```js + class EditableStructureExample extends React.Component { + constructor(props) { + super(props) + + this.state = { + districtsData: { + d1: { + label: 'District 1', + schools: ['s1'] + } + }, + schoolsData: { + s1: { + label: 'School 1', + districtId: 'd1', + isSelected: false + } + } + } -class EditableStructureExample extends React.Component { - constructor(props) { - super(props) + this.districtCounter = Object.keys(this.state.districtsData).length + this.schoolCounter = Object.keys(this.state.schoolsData).length + } - this.state = { - districtsData: { - d1: { - label: 'District 1', - schools: ['s1'] + toggleSelectedSchool(id, school) { + this.setState({ + schoolsData: { + ...this.state.schoolsData, + [id]: { ...school, isSelected: !school.isSelected } } - }, - schoolsData: { - s1: { - label: 'School 1', - districtId: 'd1', - isSelected: false + }) + } + + renderSelectedIcon(isSelected) { + return + } + + renderAddAction(label) { + return ( + + + + New {label} + + + ) + } + + addDistrict() { + const { districtsData } = this.state + this.districtCounter++ + const districtNumber = this.districtCounter + const districtId = `d${districtNumber}` + + this.setState({ + districtsData: { + ...districtsData, + [districtId]: { + label: `District ${districtNumber}`, + schools: [] + } } - } + }) } - this.districtCounter = Object.keys(this.state.districtsData).length - this.schoolCounter = Object.keys(this.state.schoolsData).length - } + addSchool(districtId) { + const { districtsData, schoolsData } = this.state + const district = districtsData[districtId] - toggleSelectedSchool(id, school) { - this.setState({ schoolsData: { - ...this.state.schoolsData, - [id]: { ...school, isSelected: !school.isSelected } - } }) - } + this.schoolCounter++ + const schoolNumber = this.schoolCounter + const schoolId = `s${schoolNumber}` - renderSelectedIcon(isSelected) { - return ( - - ) + this.setState({ + districtsData: { + ...districtsData, + [districtId]: { + ...district, + schools: [...district.schools, schoolId] + } + }, + schoolsData: { + ...schoolsData, + [schoolId]: { + label: `School ${schoolNumber}`, + districtId, + isSelected: false + } + } + }) + } + + renderDeleteOption(type, label, idToDelete) { + const id = type === 'school' ? 'deleteSchool' : 'deleteDistrict' + const callback = + type === 'school' ? this.deleteSchool : this.deleteDistrict + const separatorId = `${idToDelete}__separator` + + return [ + , + { + callback(idToDelete) + }} + themeOverride={(_componentTheme, currentTheme) => { + return { color: currentTheme.colors.textDanger } + }} + > + + + Delete {label} + + + ] + } + + deleteSchool = (id) => { + const { districtsData, schoolsData } = this.state + const { [id]: school, ...restSchools } = schoolsData + const { districtId } = school + const district = districtsData[districtId] + + this.setState({ + schoolsData: restSchools, + districtsData: { + ...districtsData, + [districtId]: { + ...district, + schools: district.schools.filter((schoolId) => schoolId !== id) + } + } + }) + } + + deleteDistrict = (id) => { + const { districtsData, schoolsData } = this.state + const { [id]: district, ...restDistricts } = districtsData + + const filteredSchools = {} + + Object.entries(schoolsData).forEach(([schoolId, school]) => { + if (school.districtId !== id) { + filteredSchools[schoolId] = school + } + }) + + this.setState({ + districtsData: restDistricts, + schoolsData: filteredSchools + }) + } + + render() { + const { districtsData, schoolsData } = this.state + const districts = Object.entries(districtsData) + const schools = Object.entries(schoolsData) + + return ( + + { + this.addDistrict() + }} + > + {districts.map(([id, district]) => { + const { label } = district + return ( + + {label} + + ) + })} + + + {districts.map(([districtId, district]) => { + const { label, schools } = district + return ( + { + this.addSchool(districtId) + }} + > + {schools.map((id) => { + const { label, isSelected } = this.state.schoolsData[id] + return ( + + {label} + + ) + })} + {this.renderDeleteOption('district', label, districtId)} + + ) + })} + + {schools.map(([id, school]) => { + const { label, isSelected } = school + return ( + + { + this.toggleSelectedSchool(id, school) + }} + > + {isSelected ? 'Deselect' : 'Select'} {label} + + {this.renderDeleteOption('school', label, id)} + + ) + })} + + ) + } } - renderAddAction(label) { - return ( - - - New {label} - + render() + ``` + +- ```js + const EditableStructureExample = () => { + const [districtsData, setDistrictsData] = useState({ + d1: { + label: 'District 1', + schools: ['s1'] + } + }) + const [schoolsData, setSchoolsData] = useState({ + s1: { + label: 'School 1', + districtId: 'd1', + isSelected: false + } + }) + + const [districtCounter, setDistrictCounter] = useState( + Object.keys(districtsData).length ) - } + const [schoolCounter, setSchoolCounter] = useState( + Object.keys(schoolsData).length + ) + + const districts = Object.entries(districtsData) + const schools = Object.entries(schoolsData) + + const toggleSelectedSchool = (id, school) => { + setSchoolsData({ + ...schoolsData, + [id]: { ...school, isSelected: !school.isSelected } + }) + } + + const renderSelectedIcon = (isSelected) => { + return + } + + const renderAddAction = (label) => { + return ( + + + + New {label} + + + ) + } - addDistrict() { - const { districtsData } = this.state - this.districtCounter++ - const districtNumber = this.districtCounter - const districtId = `d${districtNumber}` + const addDistrict = () => { + const newDistrictCounter = districtCounter + 1 + const districtNumber = newDistrictCounter + const districtId = `d${newDistrictCounter}` + setDistrictCounter(newDistrictCounter) - this.setState({ - districtsData: { + setDistrictsData((districtsData) => ({ ...districtsData, [districtId]: { label: `District ${districtNumber}`, schools: [] } - } - }) - } - - addSchool(districtId) { - const { districtsData, schoolsData } = this.state - const district = districtsData[districtId] + })) + } - this.schoolCounter++ - const schoolNumber = this.schoolCounter - const schoolId = `s${schoolNumber}` + const addSchool = (districtId) => { + const newSchoolCounter = schoolCounter + 1 + const district = districtsData[districtId] + const schoolNumber = newSchoolCounter + const schoolId = `s${newSchoolCounter}` + setSchoolCounter(newSchoolCounter) - this.setState({ - districtsData: { + setDistrictsData((districtsData) => ({ ...districtsData, [districtId]: { - ...district, + ...districtsData[districtId], schools: [...district.schools, schoolId] } - }, - schoolsData: { + })) + + setSchoolsData((schoolsData) => ({ ...schoolsData, [schoolId]: { label: `School ${schoolNumber}`, districtId, isSelected: false } - } - }) - } + })) + } - renderDeleteOption(type, label, idToDelete) { - const id = type === 'school' ? "deleteSchool" : "deleteDistrict" - const callback = type === 'school' ? this.deleteSchool : this.deleteDistrict - const separatorId = `${idToDelete}__separator` - - return [ - , - { callback(idToDelete) }} - themeOverride={(_componentTheme, currentTheme) => { - return { color: currentTheme.colors.textDanger } - }} - > - - - Delete {label} - - - ] - } + const renderDeleteOption = (type, label, idToDelete) => { + const id = type === 'school' ? 'deleteSchool' : 'deleteDistrict' + const callback = type === 'school' ? deleteSchool : deleteDistrict + const separatorId = `${idToDelete}__separator` + + return [ + , + { + callback(idToDelete) + }} + themeOverride={(_componentTheme, currentTheme) => { + return { color: currentTheme.colors.textDanger } + }} + > + + + Delete {label} + + + ] + } - deleteSchool = (id) => { - const { districtsData, schoolsData } = this.state - const { [id]: school, ...restSchools } = schoolsData - const { districtId } = school - const district = districtsData[districtId] + const deleteSchool = (id) => { + const { [id]: school, ...restSchools } = schoolsData + const { districtId } = school + const district = districtsData[districtId] - this.setState({ - schoolsData: restSchools, - districtsData: { + setSchoolsData(restSchools) + setDistrictsData({ ...districtsData, [districtId]: { ...district, - schools: district.schools.filter(schoolId => schoolId !== id) + schools: district.schools.filter((schoolId) => schoolId !== id) } - } - }) - } - - deleteDistrict = (id) => { - const { districtsData, schoolsData } = this.state - const { [id]: district, ...restDistricts } = districtsData + }) + } - const filteredSchools = {} + const deleteDistrict = (id) => { + const { [id]: district, ...restDistricts } = districtsData - Object.entries(schoolsData).forEach(([schoolId, school]) => { - if (school.districtId !== id) { - filteredSchools[schoolId] = school - } - }) + const filteredSchools = {} - this.setState({ - districtsData: restDistricts, - schoolsData: filteredSchools - }) - } + Object.entries(schoolsData).forEach(([schoolId, school]) => { + if (school.districtId !== id) { + filteredSchools[schoolId] = school + } + }) - render() { - const { districtsData, schoolsData } = this.state - const districts = Object.entries(districtsData) - const schools = Object.entries(schoolsData) + setDistrictsData(restDistricts) + setSchoolsData(filteredSchools) + } return ( - + { - this.addDistrict() + addDistrict() }} > {districts.map(([id, district]) => { @@ -1421,32 +2398,32 @@ class EditableStructureExample extends React.Component { })} - {districts.map(([districtId , district]) => { + {districts.map(([districtId, district]) => { const { label, schools } = district return ( { - this.addSchool(districtId) + addSchool(districtId) }} > {schools.map((id) => { - const { label, isSelected } = this.state.schoolsData[id] + const { label, isSelected } = schoolsData[id] return ( {label} ) })} - {this.renderDeleteOption('district', label, districtId)} + {renderDeleteOption('district', label, districtId)} ) })} @@ -1461,24 +2438,23 @@ class EditableStructureExample extends React.Component { > { - this.toggleSelectedSchool(id, school) + toggleSelectedSchool(id, school) }} > {isSelected ? 'Deselect' : 'Select'} {label} - {this.renderDeleteOption('school', label, id)} + {renderDeleteOption('school', label, id)} ) })} ) } -} -render() -``` + render() + ``` ### Guidelines