Skip to content

Commit

Permalink
feat: enhance app card style (#812)
Browse files Browse the repository at this point in the history
Co-authored-by: spaenleh <[email protected]>
  • Loading branch information
LinaYahya and spaenleh authored Oct 11, 2023
1 parent 389230b commit 33dd377
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 121 deletions.
1 change: 1 addition & 0 deletions cypress/support/commands/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ Cypress.Commands.add(
} else if (custom) {
cy.get(`#${buildItemFormAppOptionId(name)}`).click();
// check name get added automatically
cy.fillBaseItemModal({ name }, { confirm: false });
cy.get(`#${CUSTOM_APP_URL_ID}`).type(CUSTOM_APP_URL);
} else {
cy.get(`#${buildItemFormAppOptionId(name)}`).click();
Expand Down
137 changes: 63 additions & 74 deletions src/components/item/form/AppForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useState } from 'react';
import { useState } from 'react';

import CloseIcon from '@mui/icons-material/Close';
import { Box, IconButton, InputAdornment, TextField } from '@mui/material';
import { Box, Stack, TextField } from '@mui/material';
import Skeleton from '@mui/material/Skeleton';
import Typography from '@mui/material/Typography';
import Grid2 from '@mui/material/Unstable_Grid2/Grid2';

import { AppItemType, DiscriminatedItem } from '@graasp/sdk';
import { AppRecord } from '@graasp/sdk/frontend';
import { DiscriminatedItem } from '@graasp/sdk';
import { Button } from '@graasp/ui';

import AppCard from '@/components/main/AppCard';
import { CUSTOM_APP_URL_ID } from '@/config/selectors';
Expand All @@ -15,128 +15,117 @@ import { useBuilderTranslation } from '../../../config/i18n';
import { hooks } from '../../../config/queryClient';
import { BUILDER } from '../../../langs/constants';
import { buildAppExtra } from '../../../utils/itemExtra';
import BaseItemForm from './NameForm';
import NameForm from './NameForm';

type Props = {
onChange: (item: Partial<DiscriminatedItem>) => void;
item?: AppItemType;
updatedProperties: Partial<DiscriminatedItem>;
};

const AppForm = ({
onChange,
item,
updatedProperties = {},
}: Props): JSX.Element => {
const AppForm = ({ onChange, updatedProperties = {} }: Props): JSX.Element => {
const { t: translateBuilder } = useBuilderTranslation();
const [newName, setNewName] = useState<string>(item?.name ?? '');
const [isCustomApp, setIsCustomApp] = useState<boolean>(false);

const handleAppSelection = (
newValue: AppRecord | null | { url: string; name: string },
newValue: null | { url: string; name: string },
) => {
if (!newValue) {
return console.error('new value is undefined');
// there is a new value to use
if (newValue) {
onChange({
name: newValue.name,
// todo: use better type here (partial of discriminated item is not a good type)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
extra: buildAppExtra({
url: newValue.url,
}),
});
return;
}

const url = newValue?.url;
const name = newValue?.name ?? item?.name;
// TODO: improve types
const props = {
...item,
extra: buildAppExtra({ url }),
} as unknown as AppItemType;
if (name) {
setNewName(name);
props.name = name;
// there is no new value to use
if (!newValue) {
// unset the name and the url in the extra
onChange({ name: undefined, extra: undefined });
}
return onChange(props);
};

const { useApps } = hooks;
const { data, isLoading: isAppsLoading } = useApps();

const url = (updatedProperties?.extra?.app as { url: string })?.url;
const currentUrl = (updatedProperties?.extra?.app as { url: string })?.url;

const addCustomApp = () => {
setIsCustomApp(true);
handleAppSelection({ url: '', name: '' });
// maybe here we would like to not reset the name ?
handleAppSelection(null);
};

if (isAppsLoading) {
return <Skeleton height={60} />;
}
return (
<div>
<Box>
<Typography variant="h6">
{translateBuilder(BUILDER.CREATE_NEW_ITEM_APP_TITLE)}
</Typography>
<BaseItemForm
setChanges={onChange}
updatedProperties={
{
...item,
name: newName,
...updatedProperties,
} as Partial<DiscriminatedItem>
}
/>
<br />

<NameForm setChanges={onChange} updatedProperties={updatedProperties} />

{isCustomApp ? (
<Box sx={{ mt: 3 }}>
<Stack direction="column" alignItems="start" mt={1} spacing={2}>
<TextField
id={CUSTOM_APP_URL_ID}
fullWidth
variant="standard"
autoFocus
label={translateBuilder(BUILDER.APP_URL)}
onChange={(e) =>
handleAppSelection({ url: e.target.value, name: '' })
// todo: use better type here (partial of discriminated item is not a good type)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
onChange({ extra: buildAppExtra({ url: e.target.value }) })
}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => {
setIsCustomApp(false);
handleAppSelection({ url: '', name: '' });
}}
>
<CloseIcon />
</IconButton>
</InputAdornment>
),
}}
value={currentUrl}
/>
</Box>
<Button
variant="text"
onClick={() => {
setIsCustomApp(false);
handleAppSelection(null);
}}
>
{translateBuilder(BUILDER.BACK_TO_APP_LIST)}
</Button>
</Stack>
) : (
<Box
sx={{
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: 2,
mt: 3,
}}
>
<Grid2 container spacing={2} alignItems="stretch">
{data?.map((ele) => (
<AppCard
key={ele.name}
url={ele?.url}
name={ele.name}
description={ele.description}
extra={ele?.extra}
selected={ele?.url === url}
handleSelect={handleAppSelection}
image={ele.extra.image}
selected={ele.url === currentUrl}
onClick={() => {
if (ele.url === currentUrl) {
// reset fields
handleAppSelection(null);
} else {
handleAppSelection({ url: ele.url, name: ele.name });
}
}}
/>
))}
<AppCard
name={translateBuilder(BUILDER.CREATE_CUSTOM_APP)}
handleSelect={addCustomApp}
description={translateBuilder(
BUILDER.CREATE_CUSTOM_APP_DESCRIPTION,
)}
onClick={addCustomApp}
/>
</Box>
</Grid2>
)}
</div>
</Box>
);
};

Expand Down
10 changes: 7 additions & 3 deletions src/components/item/form/NameForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ChangeEvent } from 'react';

import { TextField } from '@mui/material';
import { TextField, useMediaQuery, useTheme } from '@mui/material';

import { useBuilderTranslation } from '../../../config/i18n';
import { ITEM_FORM_NAME_INPUT_ID } from '../../../config/selectors';
Expand All @@ -18,7 +18,9 @@ const NameForm = ({
setChanges,
}: NameFormProps): JSX.Element => {
const { t: translateBuilder } = useBuilderTranslation();

const theme = useTheme();
// when the screen is large, us only half of the width for the input.
const largeScreen = useMediaQuery(theme.breakpoints.up('sm'));
const handleNameInput = (event: ChangeEvent<{ value: string }>) => {
setChanges({ name: event.target.value });
};
Expand All @@ -34,7 +36,9 @@ const NameForm = ({
onChange={handleNameInput}
// always shrink because setting name from defined app does not shrink automatically
InputLabelProps={{ shrink: true }}
sx={{ width: '50%', my: 1 }}
// only take full width when on small screen size
fullWidth={!largeScreen}
sx={{ my: 1, width: largeScreen ? '50%' : undefined }}
/>
);
};
Expand Down
67 changes: 25 additions & 42 deletions src/components/main/AppCard.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,54 @@
import React from 'react';

import CloseIcon from '@mui/icons-material/Close';
import {
Card,
CardActionArea,
CardContent,
CardMedia,
IconButton,
Typography,
} from '@mui/material';
import Grid2 from '@mui/material/Unstable_Grid2';

import { buildItemFormAppOptionId } from '@/config/selectors';

import AddNewIcon from '../../resources/addNew.png';

export type Props = {
url?: string;
description?: string;
description: string;
name: string;
extra?: { image?: string };
handleSelect: any;
image?: string;
onClick: () => void;
selected?: boolean;
};

const AppCard = ({
url,
description,
name,
extra,
handleSelect,
image,
onClick,
selected = false,
}: Props): JSX.Element => {
const clearApp = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
handleSelect({ url: '', name: '' });
};
return (
}: Props): JSX.Element => (
<Grid2 xs={12} sm={6} md={4} display="flex">
<Card
sx={{
maxWidth: 300,
border: selected ? '2px solid #5050d2' : 'none',
position: 'relative',
}}
onClick={() => {
handleSelect({ url, description, name, extra });
width: '100%',
outline: selected ? '2px solid #5050d2' : '',
}}
onClick={onClick}
id={buildItemFormAppOptionId(name)}
>
{selected && (
<IconButton
style={{
position: 'absolute',
top: 0,
right: 0,
zIndex: 1,
color: 'white',
}}
onClick={clearApp}
>
<CloseIcon />
</IconButton>
)}
<CardActionArea>
<CardActionArea
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
justifyContent: 'flex-start',
height: '100%',
}}
>
<CardMedia
component="img"
height="100"
image={extra?.image || AddNewIcon}
// replace with aspectRatio: 1 to display a square image
sx={{ height: 100 }}
image={image || AddNewIcon}
alt={name}
/>
<CardContent>
Expand All @@ -87,7 +70,7 @@ const AppCard = ({
</CardContent>
</CardActionArea>
</Card>
);
};
</Grid2>
);

export default AppCard;
3 changes: 2 additions & 1 deletion src/langs/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -256,5 +256,6 @@
"STATUS_TOOLTIP_SHOW_CHATBOX": "صندوق الدردشة مرئي",
"SETTINGS_FILE_SETTINGS_TITLE": "إعدادات الملف",
"APP_URL": "رابط التطبيق",
"CREATE_CUSTOM_APP": "أنشيء تطبيقاً خاصاً"
"CREATE_CUSTOM_APP": "أنشيء تطبيقاً خاصاً",
"BACK_TO_APP_LIST": "الرجوع إلى قائمة التطبيقات"
}
2 changes: 2 additions & 0 deletions src/langs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,4 +316,6 @@ export const BUILDER = {
APPROVE_BUTTON_TEXT: 'APPROVE_BUTTON_TEXT',
APP_URL: 'APP_URL',
CREATE_CUSTOM_APP: 'CREATE_CUSTOM_APP',
CREATE_CUSTOM_APP_DESCRIPTION: 'CREATE_CUSTOM_APP_DESCRIPTION',
BACK_TO_APP_LIST: 'BACK_TO_APP_LIST',
};
4 changes: 3 additions & 1 deletion src/langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -256,5 +256,7 @@
"DELETE_LAST_ADMIN_ALERT_MESSAGE": "You are not allowed to delete this admin as this is the only admin",
"APPROVE_BUTTON_TEXT": "OK",
"APP_URL": "App Url",
"CREATE_CUSTOM_APP": "Add Your Custom App"
"CREATE_CUSTOM_APP": "Add Your Custom App",
"CREATE_CUSTOM_APP_DESCRIPTION": "Advanced option to create a custom app",
"BACK_TO_APP_LIST": "Back To App's List"
}

0 comments on commit 33dd377

Please sign in to comment.