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

Dialog+tabs #782

Merged
merged 31 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
acd4c4c
add tab component
OlegMoshkovich Aug 4, 2023
6ee81cb
add tabs component to the library
OlegMoshkovich Aug 4, 2023
6e620fc
comments
OlegMoshkovich Aug 4, 2023
9ecefdf
comments
OlegMoshkovich Aug 4, 2023
779ccfa
add callback
OlegMoshkovich Aug 4, 2023
fc5df12
fix the boxes
OlegMoshkovich Aug 4, 2023
57fdc45
chnages
OlegMoshkovich Aug 4, 2023
5629c6a
add tabs dialog
OlegMoshkovich Aug 5, 2023
88820d6
add tabs to a dialog
OlegMoshkovich Aug 4, 2023
51a849f
reworked the dialog
OlegMoshkovich Aug 9, 2023
fc80c6d
organize component props
OlegMoshkovich Aug 9, 2023
678cc57
edit component
OlegMoshkovich Aug 9, 2023
682f462
clean up
OlegMoshkovich Aug 9, 2023
9800bd3
clean up
OlegMoshkovich Aug 9, 2023
791c529
adjust the theme
OlegMoshkovich Aug 9, 2023
2cc1879
spacing clean up
OlegMoshkovich Aug 9, 2023
864c446
add array of call backs
OlegMoshkovich Aug 9, 2023
da6e022
clean up
OlegMoshkovich Aug 9, 2023
16a91c1
adjustments
OlegMoshkovich Aug 9, 2023
f8dd62e
add tab fixture and add scrollable prop the dialog
OlegMoshkovich Aug 9, 2023
cd66949
correct a typo
OlegMoshkovich Aug 10, 2023
53a4525
address the comments
OlegMoshkovich Aug 11, 2023
e6373d0
address comments
OlegMoshkovich Aug 11, 2023
ccf705a
Fixes and simplifications.
pablo-mayrgundter Aug 12, 2023
324fc33
Restore DialogActions to TabbedDialog. Enhance asserts
pablo-mayrgundter Aug 14, 2023
4f9fa9b
Fixup title
pablo-mayrgundter Aug 14, 2023
4694f22
Add TabbedDialog.test.jsx
pablo-mayrgundter Aug 17, 2023
618af66
Merge branch 'main' into dialog+tabs
pablo-mayrgundter Aug 17, 2023
ff64f70
Merge remote-tracking branch 'upstream/main' into dialog+tabs
pablo-mayrgundter Aug 17, 2023
071721a
tsc fixups.
pablo-mayrgundter Aug 17, 2023
877a6a1
Merge remote-tracking branch 'origin/dialog+tabs' into dialog+tabs
pablo-mayrgundter Aug 17, 2023
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: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bldrs",
"version": "1.0.0-r689",
"version": "1.0.0-r705",
"main": "src/index.jsx",
"license": "MIT",
"homepage": "https://github.com/bldrs-ai/Share",
Expand Down
12 changes: 8 additions & 4 deletions src/Components/Buttons.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ export function CloseButton({onClick}) {
*
* @property {string} title Text to show in button
* @property {Function} onClick callback
* @property {object} icon Start icon to left of text
* @property {boolean} border Default: false
* @property {boolean} background Default: true
* @property {object} [icon] Start icon to left of text
* @property {boolean} [border] Default: false
* @property {boolean} [background] Default: true
* @return {object} React component
*/
export function RectangularButton({
Expand All @@ -129,7 +129,11 @@ export function RectangularButton({
background = true,
}) {
assertDefined(title, onClick)
return <Button onClick={onClick} startIcon={icon} variant='rectangular'>{title}</Button>
return (
icon ?
<Button onClick={onClick} startIcon={icon} variant='rectangular'>{title}</Button> :
<Button onClick={onClick} variant='rectangular'>{title}</Button>
)
}


Expand Down
2 changes: 1 addition & 1 deletion src/Components/Dialog.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react'
import MuiDialog from '@mui/material/Dialog'
import DialogActions from '@mui/material/DialogActions'
import DialogContent from '@mui/material/DialogContent'
import DialogTitle from '@mui/material/DialogTitle'
import MuiDialog from '@mui/material/Dialog'
import {RectangularButton, CloseButton} from '../Components/Buttons'
import {assertDefined} from '../utils/assert'

Expand Down
31 changes: 31 additions & 0 deletions src/Components/TabbedDialog.fixture.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* eslint-disable no-magic-numbers */
import React from 'react'
import FixtureContext from '../FixtureContext'
import debug from '../utils/debug'
import TabbedDialog from './TabbedDialog'


const loremIpsum = (size) => `Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. `.repeat(size)


export default (
<FixtureContext>
<TabbedDialog
tabLabels={['Explore', 'Open', 'Save']}
headerLabels={['Explore Sample Projects', 'Open Project', 'Save Project']}
contentComponents={[
(<p key='1'>{loremIpsum(3)}</p>),
(<p key='2'>{loremIpsum(2)}</p>),
(<p key='3'>{loremIpsum(4)}</p>),
]}
actionCbs={[
() => debug().log('clicked 1'),
() => debug().log('clicked 2'),
() => debug().log('clicked 3'),
]}
isDialogDisplayed={true}
setIsDialogDisplayed={() => debug().log('setIsDialogDisplayed')}
OlegMoshkovich marked this conversation as resolved.
Show resolved Hide resolved
/>
</FixtureContext>
)
66 changes: 66 additions & 0 deletions src/Components/TabbedDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, {useState} from 'react'
import MuiDialog from '@mui/material/Dialog'
import DialogActions from '@mui/material/DialogActions'
import DialogContent from '@mui/material/DialogContent'
import DialogTitle from '@mui/material/DialogTitle'
import {assertDefined, assertArraysEqualLength} from '../utils/assert'
import {CloseButton, RectangularButton} from './Buttons'
import Tabs from './Tabs'


/**
* A Dialog with tabs to page between associated contents.
*
* @property {Array<string>} tabLabels Tab names
* @property {Array<string>} headerLabels Short messages describing the current operation
* @property {Array<React.Element>} contentComponents Components coresponding to the tabs
* @property {Array<React.Element>} actionCbs Callbacks for each component's ok button
* @property {boolean} isDialogDisplayed React var
* @property {Function} setIsDialogDisplayed React setter
* @property {boolean} [isTabsScrollable] Activate if the number of tabs is larger than 5
* @property {React.ReactElement} [icon] Leading icon above header description
* @property {string} [actionButtonLabels] Labels for action ok buttons
* @return {React.Component}
*/
export default function TabbedDialog({
tabLabels,
headerLabels,
contentComponents,
actionCbs,
isDialogDisplayed,
setIsDialogDisplayed,
isTabsScrollable = false,
icon,
actionButtonLabels,
}) {
assertDefined(tabLabels, headerLabels, contentComponents, actionCbs, isDialogDisplayed, setIsDialogDisplayed)
assertArraysEqualLength(tabLabels, headerLabels, contentComponents, actionCbs)
const close = () => setIsDialogDisplayed(false)
const [currentTab, setCurrentTab] = useState(0)
return (
<MuiDialog
maxWidth={'xs'}
open={isDialogDisplayed}
onClose={close}
PaperProps={{variant: 'control'}}
>
<CloseButton onClick={close}/>
<DialogTitle>
{icon && <>{icon}<br/></>}
{headerLabels[currentTab]}
</DialogTitle>
<DialogContent>
<Tabs tabLabels={tabLabels} actionCb={setCurrentTab} isScrollable={isTabsScrollable}/>
</DialogContent>
<DialogContent>
{contentComponents[currentTab]}
</DialogContent>
<DialogActions>
<RectangularButton
title={actionButtonLabels ? actionButtonLabels[currentTab] : 'Ok'}
onClick={actionCbs[currentTab]}
/>
</DialogActions>
</MuiDialog>
)
}
43 changes: 43 additions & 0 deletions src/Components/TabbedDialog.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react'
import TabbedDialog from './TabbedDialog'


describe('TabbedDialog', () => {
it('', () => {
const cb1 = jest.fn()
const cb2 = jest.fn()
const cb3 = jest.fn()
render(
<TabbedDialog
tabLabels={['Explore', 'Open', 'Save']}
headerLabels={['Explore Sample Projects', 'Open Project', 'Save Project']}
contentComponents={[
(<p key='1'>{'A content'}</p>),
(<p key='2'>{'B content'}</p>),
(<p key='3'>{'C content'}</p>),
]}
actionCbs={[cb1, cb2, cb3]}
actionButtonLabels={['A OK', 'B OK', 'C OK']}
isDialogDisplayed={true}
setIsDialogDisplayed={jest.fn()}
/>)

fireEvent.click(screen.getByText('A OK'))
expect(cb1.mock.calls.length).toBe(1)
expect(cb2.mock.calls.length).toBe(0)
expect(cb3.mock.calls.length).toBe(0)

fireEvent.click(screen.getByText('Open'))
fireEvent.click(screen.getByText('B OK'))
expect(cb1.mock.calls.length).toBe(1)
expect(cb2.mock.calls.length).toBe(1)
expect(cb3.mock.calls.length).toBe(0)

fireEvent.click(screen.getByText('Save'))
fireEvent.click(screen.getByText('C OK'))
expect(cb1.mock.calls.length).toBe(1)
expect(cb2.mock.calls.length).toBe(1)
expect(cb3.mock.calls.length).toBe(1)
})
})
14 changes: 14 additions & 0 deletions src/Components/Tabs.fixture.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react'
import Tabs from './Tabs'
import FixtureContext from '../FixtureContext'
import debug from '../utils/debug'


export default (
<FixtureContext>
<Tabs
tabLabels={['Explore', 'Open', 'Save']}
actionCb={() => debug().log('Clicked')}
/>
</FixtureContext>
)
25 changes: 25 additions & 0 deletions src/Components/Tabs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, {useState} from 'react'
import MuiTabs from '@mui/material/Tabs'
import Tab from '@mui/material/Tab'
import {assertDefined} from '../utils/assert'


/**
* @property {Array<string>} tabLabels Names of each tab
* @property {Function} actionCb callBack fired when the tabs is selected, returns currect tab number
* @property {boolean} [isScrollable] Enable scrolling for many (> 5) tabs
* @return {React.Component}
*/
export default function Tabs({tabLabels, actionCb, isScrollable = false}) {
assertDefined(tabLabels, actionCb)
const [value, setValue] = useState(0)
const handleChange = (event, newValue) => {
setValue(newValue)
actionCb(newValue)
}
return (
<MuiTabs value={value} onChange={handleChange} centered variant={isScrollable ? 'scrollable' : 'fullWidth'}>
{tabLabels.map((tab) => <Tab key={tab} label={tab}/>)}
</MuiTabs>
)
}
4 changes: 2 additions & 2 deletions src/Styles.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function Styles({theme}) {
},
'.MuiDialog-paper': {
textAlign: 'center',
padding: '1em 0.5em',
padding: '0.5em',
},
'.MuiDialog-paper > .MuiButtonBase-root': {
position: 'absolute',
Expand Down Expand Up @@ -65,7 +65,7 @@ export default function Styles({theme}) {
height: '12px',
},
'*::-webkit-scrollbar': {
width: '10px',
width: '2px',
background: theme.palette.secondary.background,
},
'*::-webkit-scrollbar-thumb': {
Expand Down
53 changes: 52 additions & 1 deletion src/theme/Components.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @param {object} Mui color palette.
* @return {object} Mui component overrides.
*/
export function getComponentOverrides(palette) {
export function getComponentOverrides(palette, typography) {
return {
MuiTreeItem: {
styleOverrides: {
Expand Down Expand Up @@ -55,6 +55,19 @@ export function getComponentOverrides(palette) {
},
},
},
MuiDialog: {
styleOverrides: {
root: {
},
},
},
MuiDialogContent: {
styleOverrides: {
root: {
padding: '0px 10px',
},
},
},
MuiPaper: {
styleOverrides: {
root: {
Expand All @@ -75,6 +88,44 @@ export function getComponentOverrides(palette) {
},
],
},
MuiTab: {
styleOverrides: {
root: {
'textTransform': 'none',
'minWidth': 0,
'fontSize': '.9em',
'fontWeight': typography.fontWeight,
'marginRight': 0,
'color': palette.primary.contrastText,
'fontFamily': typography.fontFamily,
'&:hover': {
color: palette.secondary.main,
},
'&.Mui-selected': {
color: palette.secondary.main,
fontWeight: typography.fontWeight,
},
'&.Mui-focusVisible': {
backgroundColor: 'green',
},
'@media (max-width: 700px)': {
fontSize: '.8em',
},
},
},
},
MuiTabs: {
styleOverrides: {
root: {
'paddingBottom': '12px',
'width': '100%',
'& .MuiTabs-indicator': {
backgroundColor: palette.secondary.main,
},
},
},
},

MuiCardActions: {
styleOverrides: {
root: {
Expand Down
2 changes: 1 addition & 1 deletion src/theme/Theme.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function loadTheme(mode, setMode, themeChangeListeners) {
// https://mui.com/customization/dark-mode/
const activePalette = mode === Themes.Day ? day : night
const theme = {
components: getComponentOverrides(activePalette),
components: getComponentOverrides(activePalette, getTypography()),
typography: getTypography(),
shape: {borderRadius: 8},
palette: activePalette,
Expand Down
1 change: 1 addition & 0 deletions src/theme/Typography.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function getTypography() {
fontFamily: fontFamily,
fontSize: fontSize,
letterSpacing: letterSpacing,
fontWeight: fontWeight,
lineHeight: lineHeight,
h1: {fontSize: '1.3em', fontWeight},
h2: {fontSize: '1.2em', fontWeight},
Expand Down
25 changes: 25 additions & 0 deletions src/utils/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export function assertDefined(...args) {
if (Object.prototype.hasOwnProperty.call(args, ndx)) {
const arg = args[ndx]
assert(arg !== null && arg !== undefined, `Arg ${ndx} is not defined`)
if (Array.isArray(arg)) {
for (let i = 0; i < arg.length; i++) {
assertDefined(arg[i], `Arg ${ndx} is an array with undefined index ${i}`)
}
}
}
}
if (args.length === 1) {
Expand All @@ -44,3 +49,23 @@ export function assertDefinedBoolean(arg) {
}
return false
}


/**
* @param {any} arrays Variable length arguments to assert are defined.
* @return {Array<Array<any>>} The arrays
*/
export function assertArraysEqualLength(...arrays) {
if (arrays.length <= 1) {
throw new Error('Expected multiple arrays')
}
const arrLength = arrays[0].length
for (const ndx in arrays) {
if (Object.prototype.hasOwnProperty.call(arrays, ndx)) {
const array = arrays[ndx]
assertDefined(array)
assert(arrLength === array.length, `Array ${ndx} has unexpected length != ${array.length}`)
}
}
return arrays
}
Loading
Loading