Skip to content
Merged
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
6 changes: 3 additions & 3 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
"@emotion/react": "^11.10.0",
"@emotion/styled": "^11.10.0",
"@mui/material": "^5.10.0",
"next": "12.1.0",
"react": "17.0.2",
"react-dom": "17.0.2"
"next": "^12.2.4",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@baseapp-frontend/config": "*",
Expand Down
50 changes: 47 additions & 3 deletions apps/docs/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,54 @@
import { ButtonWithLoading } from '@baseapp-frontend/design-system-mui'
import {
ButtonWithLoading,
PasswordField,
ImageUploader,
} from '@baseapp-frontend/design-system-mui'
import { Divider, useTheme } from '@mui/material'
import { useState } from 'react'

export default function Docs() {
const [images, setImages] = useState([])
const theme = useTheme()
return (
<div>
<h1>Docs</h1>
<ButtonWithLoading>button</ButtonWithLoading>
<h1>Components demo</h1>
<Divider style={{ margin: theme.spacing(2, 0) }} />

<h2>{`<ButtonWithLoading>`}</h2>
<div style={{ width: theme.spacing(48), display: 'flex', flexDirection: 'column' }}>
<ButtonWithLoading variant="contained" style={{ margin: theme.spacing(2, 0) }}>
button
</ButtonWithLoading>

<ButtonWithLoading variant="contained" loading>
button
</ButtonWithLoading>
</div>

<Divider style={{ margin: theme.spacing(2, 0) }} />

<div style={{ width: theme.spacing(48) }}>
<h2>{`<PasswordField />`}</h2>
<PasswordField />

<PasswordField helperText="Type your password." />

<PasswordField error helperText="Incorrect entry." />
</div>

<Divider style={{ margin: theme.spacing(2, 0) }} />

<h2>{`<ImageUploader />`}</h2>
<div style={{ width: theme.spacing(48) }}>
<ImageUploader
images={images}
setImages={setImages}
name="imageUploeader"
ImageProps={{ style: { outline: '1px solid blue' } }}
buttonLabel="Upload your image here"
buttonRemoveLabel="Remove"
/>
</div>
</div>
)
}
6 changes: 3 additions & 3 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@
"babel-jest": "^27.4.6",
"eslint": "^8.10.0",
"jest": "^27.4.7",
"next": "^12.1.0",
"prettier": "^2.5.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"next": "^12.2.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-test-renderer": "^17.0.2",
"ts-jest": "^27.1.3",
"typescript": "^4.5.4"
Expand Down
18 changes: 16 additions & 2 deletions packages/design-system-mui/components/ButtonWithLoading/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
import { Button } from './styled'
import { Button } from '@mui/material'
import { CircularProgress } from './styled'

const ButtonWithLoading = ({ children }) => <Button>{children} With Loading</Button>
function ButtonWithLoading({
loading = false,
children,
loadingChildren = <CircularProgress size="20px" />,
loadingColor = 'primary',
...props
}) {
return (
<Button disabled={loading} {...props}>
{children}
{loading && loadingChildren}
</Button>
)
}

export default ButtonWithLoading
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { styled } from '@mui/material/styles'
import { Button as MUIButton } from '@mui/material'
import { CircularProgress as MuiCircularProgress, CircularProgressProps } from '@mui/material'
import { FC } from 'react'

export const Button = styled(MUIButton)(({ theme }) => ({
background: theme.palette.primary.main,
}))
export const CircularProgress = styled(MuiCircularProgress)(({ theme }) => ({
position: 'absolute',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't be better if we show the loading state besides the children text? Not sure how the design looks like but seems better than overlapping the content

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can always have your own loadingChildren to overwrite

top: '50%',
left: '50%',
marginTop: '-10px',
marginLeft: '-10px',
})) as unknown as FC<CircularProgressProps>
83 changes: 83 additions & 0 deletions packages/design-system-mui/components/ImageUploader/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { FormControl, FormHelperText } from '@mui/material'
import UploadFileOutlinedIcon from '@mui/icons-material/UploadFileOutlined'
import type { IImageUploadInput } from './types'
import { ImageGroup, Image, UploaderButton, LabelGroup, ImageLabel, DeleteButton } from './styled'

function ImageUploader({
images,
setImages,
buttonLabel,
buttonRemoveLabel,
name,
error,
helperText,
ImageProps,
ImageLabelProps,
DeleteButtonProps,
UploaderButtonProps,
...props
}: IImageUploadInput) {
const deleteImage = (index: number) => {
const newImages = [...images]
newImages.splice(index, 1)
setImages(newImages)
}

function onChange(e: any): void {
//eslint-disable-line @typescript-eslint/no-explicit-any
for (let i = 0; i < e.target?.files?.length; i++) {
const file = e.target?.files[i]
const fileReader = new FileReader()
fileReader.onload = () => {
setImages([{ file: file, imagePreviewUrl: fileReader.result }])
}
fileReader.readAsDataURL(file)
}
}

return (
<FormControl error={error} fullWidth>
<UploaderButton
component="label"
htmlFor={'imageInput' + name}
variant="outlined"
color="primary"
sx={{ display: images.length ? 'none' : 'auto' }}
{...UploaderButtonProps}
>
<UploadFileOutlinedIcon sx={{ marginRight: 1 }} />
{buttonLabel || 'Upload Photo'}
</UploaderButton>
<input
name={name}
id={'imageInput' + name}
type="file"
accept="image/*"
onChange={onChange}
style={{ display: 'none' }}
{...props}
/>
{error && <FormHelperText>{helperText}</FormHelperText>}
{images.map((img, index) => (
<ImageGroup key={index}>
<Image src={img.imagePreviewUrl as string} alt="preview" {...ImageProps} />
<LabelGroup>
<ImageLabel variant="caption" {...ImageLabelProps}>
{img.file.name}
</ImageLabel>
<DeleteButton
onClick={() => deleteImage(index)}
variant="text"
color="primary"
{...DeleteButtonProps}
>
{buttonRemoveLabel || 'Remove Photo'}
</DeleteButton>
</LabelGroup>
</ImageGroup>
))}
</FormControl>
)
}

export default ImageUploader
53 changes: 53 additions & 0 deletions packages/design-system-mui/components/ImageUploader/styled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { styled } from '@mui/material/styles'
import { Button as MuiButton, Typography } from '@mui/material'
import { FC } from 'react'
import { fontSize } from '../../styles/utils'

const UploaderButton = styled(MuiButton)(({ theme }) => ({
width: '100%',
marginTop: theme.spacing(2),
border: `1px dashed ${theme.palette.grey[500]}`,
borderRadius: '8px',
height: '46px',
color: theme.palette.grey[500],
textTransform: 'capitalize',
})) as unknown as typeof MuiButton

const ImageGroup = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
marginTop: theme.spacing(2),
})) as unknown as FC

const Image = styled('img')(({ theme }) => ({
width: '87px',
height: '87px',
objectFit: 'cover',
borderRadius: theme.spacing(1),
})) as unknown as FC<JSX.IntrinsicElements['img']>

const LabelGroup = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
justifyContent: 'flex-start',
marginLeft: theme.spacing(1),
})) as unknown as FC

const ImageLabel = styled(Typography)(({ theme }) => ({
color: theme.palette.grey[500],
fontSize: fontSize(16),
padding: theme.spacing(1),
})) as unknown as typeof Typography

const DeleteButton = styled(MuiButton)(({ theme }) => ({
color: theme.palette.warning[500],
textDecoration: 'underline',
textTransform: 'capitalize',
fontSize: fontSize(14),
'&:hover': {
color: theme.palette.warning[200],
},
})) as unknown as typeof MuiButton

export { UploaderButton, ImageGroup, Image, LabelGroup, ImageLabel, DeleteButton }
24 changes: 24 additions & 0 deletions packages/design-system-mui/components/ImageUploader/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export interface IInputBaseComponentProps
extends React.HTMLAttributes<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement> {
// Accommodate arbitrary additional props coming from the `IInputProps` prop
[arbitrary: string]: any
}

export interface IInputProps extends IInputBaseComponentProps {
component?: React.ElementType<IInputBaseComponentProps> | React.FC<any>
templateComponent?: React.FC<any>
name: string
label?: string
helperText?: string
}

interface IImageUploadInput extends IInputProps {
ImageProps?: IImageProps
ImageLabelProps?: IImageLabelProps
DeleteButtonProps?: IImageDeleteButtonProps
}

interface ImageFile {
file: File
imagePreviewUrl: string | ArrayBuffer | null
}
55 changes: 55 additions & 0 deletions packages/design-system-mui/components/PasswordField/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useState, ReactElement } from 'react'
import IconButton from '@mui/material/IconButton'
import InputAdornment from '@mui/material/InputAdornment'
import Visibility from '@mui/icons-material/Visibility'
import VisibilityOff from '@mui/icons-material/VisibilityOff'
import { TextField } from './styled'
import { useTheme } from '@mui/material'
import { IPasswordFieldProps } from './types'

function PasswordField({
name = 'password',
visibilityIconColor,
InputLabelProps,
InputProps,
...props
}: IPasswordFieldProps): ReactElement {
const [viewPassword, setViewPassword] = useState(false)
const theme = useTheme()
return (
<TextField
label="Password"
placeholder="Password"
type={viewPassword ? 'text' : 'password'}
name={name}
InputLabelProps={{ ...InputLabelProps }}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => setViewPassword((state: any) => !state)}
edge="end"
>
{viewPassword ? (
<VisibilityOff
htmlColor={visibilityIconColor || theme.palette.grey[500]}
sx={{ marginRight: '4px' }}
/>
) : (
<Visibility
htmlColor={visibilityIconColor || theme.palette.grey[500]}
sx={{ marginRight: '4px' }}
/>
)}
</IconButton>
</InputAdornment>
),
...InputProps,
}}
{...props}
/>
)
}

export default PasswordField
10 changes: 10 additions & 0 deletions packages/design-system-mui/components/PasswordField/styled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { styled, makeStyles } from '@mui/material/styles'
import { TextField as MuiITextField, TextFieldProps } from '@mui/material'
import { FC } from 'react'

const TextField = styled(MuiITextField)(({ theme }) => ({
width: '100%',
marginBottom: theme.spacing(2),
})) as unknown as FC<TextFieldProps>

export { TextField }
12 changes: 12 additions & 0 deletions packages/design-system-mui/components/PasswordField/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { TextFieldProps } from '@mui/material'

export interface IPasswordFieldProps extends ITextFieldProps {
component?: React.ElementType<ITextFieldProps> | React.FC
name?: string
label?: string
helperText?: string
InputLabelProps?: TextFieldProps['InputLabelProps']
InputProps?: TextFieldProps['InputProps']
visibilityIconColor?: string
error?: boolean
}
5 changes: 5 additions & 0 deletions packages/design-system-mui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ export * from './styles/shadows'
export * from './styles/typography'

export * from './styles/theme'

export * from './styles/utils'

export { default as theme } from './styles/theme'

export * from './components/ButtonWithLoading'
export { default as ButtonWithLoading } from './components/ButtonWithLoading'
export { default as PasswordField } from './components/PasswordField'
export { default as ImageUploader } from './components/ImageUploader'
1 change: 1 addition & 0 deletions packages/design-system-mui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"dependencies": {
"@emotion/react": "^11.10.0",
"@emotion/styled": "^11.10.0",
"@mui/icons-material": "^5.8.4",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's not add this until we need

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am using that

"@mui/material": "^5.10.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
Expand Down
11 changes: 11 additions & 0 deletions packages/design-system-mui/styles/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function fontSize(fontSizeInPx: number, defaultFontSize = 16): string {
/*
- This should be used when you have a font-size value in px as reference
(i.e figma) to convert it to rem values instead;
- Should used just for 'font-size' property
- This can be useful when changing globaly the 'font-size' for accessibilty reasons;
- <body> should use this same default font-size value, 16px is usualy the default;
- avoid changing the defaultFontSize value.
*/
return `${fontSizeInPx / defaultFontSize}rem`
}
Loading