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

Add controls group #802

Closed
wants to merge 4 commits into from
Closed
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: 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-r730",
"version": "1.0.0-r702",
"main": "src/index.jsx",
"license": "MIT",
"homepage": "https://github.com/bldrs-ai/Share",
Expand Down
46 changes: 46 additions & 0 deletions src/Components/ControlsGroup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import ButtonGroup from '@mui/material/ButtonGroup'
import OpenModelControl from './OpenModelControl'
import useStore from '../store/useStore'
import {TooltipIconButton} from './Buttons'
import SearchIcon from '@mui/icons-material/Search'
import TreeIcon from '../assets/icons/Tree.svg'


/**
* Controls gropup contains visibility toggle for serach, spatial navigation and search
*
* @property {Function} fileOpen function that is passed to to the openControl for open localfiles
* @return {React.Component}
*/
export default function ControlsGroup({fileOpen}) {
const toggleIsNavigationVisible = useStore((state) => state.toggleIsNavigationVisible)
const isNavigationVisible = useStore((state) => state.isNavigationVisible)
const toggleIsSearchVisible = useStore((state) => state.toggleIsSearchVisible)
const isSearchVisible = useStore((state) => state.isSearchVisible)

return (
<ButtonGroup
orientation='horizontal'
variant='contained'
>
<OpenModelControl fileOpen={fileOpen}/>
<TooltipIconButton
title='Search'
icon={<SearchIcon className='icon-share' color='secondary'/>}
placement='bottom'
aboutInfo={false}
selected={isSearchVisible}
onClick={toggleIsSearchVisible}
/>
<TooltipIconButton
title='Navigation'
icon={<TreeIcon className='icon-share' color='secondary' style={{width: '17px', height: '17px'}}/>}
placement='bottom'
aboutInfo={false}
selected={isNavigationVisible}
onClick={toggleIsNavigationVisible}
/>
</ButtonGroup>
)
}
29 changes: 29 additions & 0 deletions src/Components/ControlsGroup.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react'
import {render, screen} from '@testing-library/react'
import ControlsGroup from './ControlsGroup'


jest.mock('./Buttons', () => ({
TooltipIconButton: (props) => <button>{props.title}</button>,
}))


describe('ControlsGroup', () => {
it('renders the search toggle', () => {
render(<ControlsGroup fileOpen={jest.fn()}/>)
const searchToggle = screen.getByText('Search')
expect(searchToggle).toBeInTheDocument()
})

it('renders the navigation toggle', () => {
render(<ControlsGroup fileOpen={jest.fn()}/>)
const navigationToggle = screen.getByText('Navigation')
expect(navigationToggle).toBeInTheDocument()
})

it('renders open model control button', () => {
render(<ControlsGroup fileOpen={jest.fn()}/>)
const navigationToggle = screen.getByText('Open IFC')
expect(navigationToggle).toBeInTheDocument()
})
})
119 changes: 94 additions & 25 deletions src/Components/InputAutocomplete.jsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,107 @@
import React from 'react'
import Paper from '@mui/material/Paper'
import Autocomplete from '@mui/material/Autocomplete'
import InputAdornment from '@mui/material/InputAdornment'
import IconButton from '@mui/material/IconButton'
import TextField from '@mui/material/TextField'
import Stack from '@mui/material/Stack'
import {assertDefined} from '../utils/assert'
import HighlightOffIcon from '@mui/icons-material/HighlightOff'
import useTheme from '@mui/styles/useTheme'


/**
* Input with autocomplete feature.
* InputAutocomplete Component.
*
* @property {Array<object>} elements suggested elements used to autocomple input,the object is in a shape of {title:'suggestion'}
* @property {string} placeholder Input placeholder
* @property {string} size MUI size of the input component
* @param {string} inputText - The input text value.
* @param {Function} setInputText - Function to set the input text.
* @param {string} error - The error message.
* @param {Function} onClear - Function to be executed when the clear button is clicked.
* @param {Array<string>} options - Array of options for the autocomplete.
* @param {React.ReactNode} startAdornment - Start adornment for the input.
* @param {string} placeholder - Placeholder text for the input.
* @return {React.Component}
*/
export default function InputAutocomplete({elements, placeholder, size = 'small'}) {
assertDefined(elements, placeholder)
function InputAutocomplete({
inputText,
setInputText,
error = '', // Set default value
onClear,
options = [],
startAdornment = null,
placeholder = '',
}) {
const theme = useTheme()

return (
<Stack spacing={3} sx={{minWidth: '280px'}}>
<Paper elevation={1} variant='control'>
<Autocomplete
multiple
options={elements}
getOptionLabel={(option) => option.title}
filterSelectedOptions
size={size}
renderInput={(params) => {
return (
<TextField
{...params}
placeholder={placeholder}
size={size}
/>
)
}
}
fullWidth
freeSolo
options={options}
value={inputText}
onChange={(_, newValue) => setInputText(newValue || '')}
onInputChange={(_, newInputValue) => setInputText(newInputValue || '')}
inputValue={inputText}
PaperComponent={({children}) => (
<Paper
sx={{
'backgroundColor': theme.palette.scene.background,
'.MuiAutocomplete-option': {
backgroundColor: theme.palette.scene.background,
},
}}
>
{children}
</Paper>
)}
renderInput={(params) => (
<TextField
{...params}
size="small"
error={!!error.length}
placeholder={placeholder}
variant="outlined"
sx={{
'width': '100%',
'border': 'none',
'& fieldset': {
border: 'none',
},
'&:hover fieldset': {
border: 'none',
},
'&.Mui-focused fieldset': {
border: 'none',
},
'& .MuiOutlinedInput-root': {
border: 'none',
height: '46px',
},
}}
InputProps={{
...params.InputProps,
startAdornment: startAdornment,
endAdornment: inputText.length > 0 ? (
<InputAdornment position="end">
<IconButton
size="small"
onClick={onClear}
style={{padding: 0, opacity: 0.8}}
>
<HighlightOffIcon
className="icon-share"
sx={{opacity: 0.8}}
size="inherit"
color="secondary"
/>
</IconButton>
</InputAdornment>
) : null,
}}
/>
)}
/>
</Stack>
</Paper>
)
}

export default InputAutocomplete
141 changes: 115 additions & 26 deletions src/Components/InputAutocomplete.test.jsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,130 @@
import React from 'react'
import {render, fireEvent} from '@testing-library/react'
import InputAutocomplete from './InputAutocomplete' // Adjust the import path
import {ThemeProvider, createTheme} from '@mui/material/styles'
import InputAutocomplete from './InputAutocomplete'


describe('InputAutocomplete', () => {
const elements = [
{title: 'Option 1'},
{title: 'Option 2'},
{title: 'Option 3'},
]

it('renders the input with placeholder', () => {
const placeholderText = 'Type something'
const {getByPlaceholderText} = render(
<InputAutocomplete elements={elements} placeholder={placeholderText}/>,
const theme = createTheme({
palette: {
scene: {
background: '#fafafa',
},
},
})

const renderWithTheme = (component) => {
return render(<ThemeProvider theme={theme}>{component}</ThemeProvider>)
}
const mockOnClear = jest.fn()
const setInputTextMock = jest.fn()
const initialText = 'Option 1'

it('renders the component', () => {
const {getByRole} = renderWithTheme(
<InputAutocomplete
inputText={initialText}
setInputText={setInputTextMock}
error=''
onClear={mockOnClear}
options={['Option 1']}
/>)
expect(getByRole('combobox')).toBeInTheDocument()
})

it('renders with the correct initial input text', () => {
const {getByRole} = renderWithTheme(
<InputAutocomplete
inputText={initialText}
setInputText={setInputTextMock}
error=''
onClear={mockOnClear}
options={['Option 1', 'Option 2']}
/>,
)
const input = getByRole('combobox')
expect(input.value).toBe(initialText)
})

it('updates the displayed input value when prop changes', () => {
const {getByRole, rerender} = renderWithTheme(
<InputAutocomplete
inputText={initialText}
setInputText={setInputTextMock}
error=''
onClear={mockOnClear}
options={['Option 1', 'Option 2']}
/>,
)

const updatedText = 'Option 2'
rerender(
<ThemeProvider theme={theme}>
<InputAutocomplete
inputText={updatedText}
setInputText={setInputTextMock}
error=''
onClear={mockOnClear}
options={['Option 1', 'Option 2']}
/>
</ThemeProvider>,
)

const input = getByRole('combobox')
expect(input.value).toBe(updatedText)
})

it('renders input with given placeholder', () => {
const placeholderText = 'Search something...'
const {getByPlaceholderText} = renderWithTheme(
<InputAutocomplete
inputText={''}
setInputText={setInputTextMock}
error=''
onClear={mockOnClear}
options={[]}
placeholder={placeholderText}
/>,
)
expect(getByPlaceholderText(placeholderText)).toBeInTheDocument()
})

it('displays the error state when error prop is passed', () => {
const errorMsg = 'Sample error'
const {getByRole} = renderWithTheme(
<InputAutocomplete
inputText={''}
setInputText={setInputTextMock}
error={errorMsg}
onClear={mockOnClear}
options={[]}
/>,
)
const inputElement = getByPlaceholderText(placeholderText)
expect(inputElement).toBeInTheDocument()
const input = getByRole('combobox')
expect(input).toHaveAttribute('aria-invalid', 'true')
})

it('displays suggestions when typing', () => {
const {getByPlaceholderText, getByText} = render(
<InputAutocomplete elements={elements} placeholder="Type something"/>,
it('displays options based on partial input string', () => {
const {getByRole, getByText} = renderWithTheme(
<InputAutocomplete
inputText=''
setInputText={setInputTextMock}
error=''
onClear={mockOnClear}
options={['Option 1', 'Option 2', 'Test 3']}
/>,
)

const inputElement = getByPlaceholderText('Type something')
const input = getByRole('combobox')

// Type some text into the input
fireEvent.change(inputElement, {target: {value: 'Option'}})
// Simulating user typing 'Opt' in the input
fireEvent.change(input, {target: {value: 'Opt'}})

// Wait for suggestions to appear
const suggestion1 = getByText('Option 1')
const suggestion2 = getByText('Option 2')
const suggestion3 = getByText('Option 3')
// Simulating focus on the input to trigger the dropdown
fireEvent.focus(input)

expect(suggestion1).toBeInTheDocument()
expect(suggestion2).toBeInTheDocument()
expect(suggestion3).toBeInTheDocument()
// Check if the options that contain 'Opt' are present in the document
expect(getByText('Option 1')).toBeInTheDocument()
expect(getByText('Option 2')).toBeInTheDocument()
})
})
1 change: 1 addition & 0 deletions src/Components/Logo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default function Logo({onClick}) {
'position': 'fixed',
'bottom': '1em',
'left': '1em',
'boxShadow': theme.shadows[1],
'& svg': {
'marginBottom': '4px',
'marginTop': '4px',
Expand Down
Loading
Loading