Skip to content

Commit

Permalink
feat: Homepage responsive design (microsoft#6762)
Browse files Browse the repository at this point in the history
* update styles

* update

* update styles

* scale image height

* update

* responsive card

* strict image cover max size, use gray background

* image fit contain

* update layout styles

* min width on header left container

Co-authored-by: Ben Yackley <[email protected]>
Co-authored-by: Srinaath Ravichandran <[email protected]>
  • Loading branch information
3 people authored Apr 13, 2021
1 parent b130831 commit b621991
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 158 deletions.
1 change: 1 addition & 0 deletions Composer/packages/client/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const headerTextContainer = css`
display: flex;
align-items: center;
justify-content: flex-start;
min-width: 600px;
width: 50%;
`;

Expand Down
104 changes: 96 additions & 8 deletions Composer/packages/client/src/pages/home/CardWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,110 @@
// Licensed under the MIT License.

/** @jsx jsx */
import { jsx } from '@emotion/core';
import React from 'react';
import { jsx, SerializedStyles } from '@emotion/core';
import React, { useState } from 'react';
import { Image, ImageFit, ImageLoadState } from 'office-ui-fabric-react/lib/Image';
import { Link } from 'office-ui-fabric-react/lib/Link';

import defaultArticleCardCover from '../../images/defaultArticleCardCover.svg';
import defaultVideoCardCover from '../../images/defaultVideoCardCover.svg';

import { ItemContainer, ItemContainerProps } from './ItemContainer';
import { itemContainerWrapper } from './styles';
import * as home from './styles';

interface CardWidgetProps extends ItemContainerProps {
export interface CardWidgetProps {
onClick?: () => void | Promise<void>;
cardType: 'resource' | 'video' | 'article';
imageCover: string;
href?: string;
target?: string;
title: string | JSX.Element;
subContent?: string;
content: string;
styles?: {
container?: SerializedStyles;
title?: SerializedStyles;
imageCover?: SerializedStyles;
content?: SerializedStyles;
moreLink?: SerializedStyles;
};
disabled?: boolean;
forwardedRef?: (project: any) => void | Promise<void>;
openExternal?: boolean;
moreLinkText?: string;
ariaLabel: string;
}

export const CardWidget: React.FC<CardWidgetProps> = (props) => {
export const CardWidget: React.FC<CardWidgetProps> = ({
onClick = undefined,
href,
target = '_blank',
title,
content,
subContent,
disabled,
forwardedRef,
openExternal,
ariaLabel,
imageCover,
cardType,
moreLinkText,
...rest
}) => {
const defaultImageCover = cardType === 'video' ? defaultVideoCardCover : defaultArticleCardCover;
const [appliedImageCover, setAppliedImageCover] = useState<string>(imageCover ?? defaultImageCover);
const [useImageBackground, setUseImageBackground] = useState(false);
const styles =
props.cardType === 'resource' ? home.cardItem : props.imageCover ? home.mediaCardItem : home.meidiaCardNoCoverItem;
const imageCover = props.imageCover || (props.cardType === 'video' ? defaultVideoCardCover : defaultArticleCardCover);
return <ItemContainer {...props} imageCover={imageCover} styles={styles} />;
rest.styles || cardType === 'resource'
? home.cardItem
: imageCover
? home.mediaCardItem
: home.meidiaCardNoCoverItem;

const onImageLoading = (state: ImageLoadState) => {
if (state === ImageLoadState.error) {
setAppliedImageCover(defaultImageCover);
}
};

// detect image cover dimention to decide apply background or not.
// By design standard image width is 244 height is 95, if feed image aspectRatio too far away will be treated as a small image.
const onImageLoaded = (e) => {
const rect = e.currentTarget.getBoundingClientRect();
const aspectRatio = rect.width / rect.height;
if (aspectRatio < 1.5) {
setUseImageBackground(true);
}
};

return (
<a
css={[itemContainerWrapper(disabled), styles.container]}
href={href}
target={target}
onClick={async (e) => {
if (onClick != null) {
e.preventDefault();
await onClick();
}
}}
{...rest}
>
<div ref={forwardedRef} aria-label={ariaLabel}>
<div css={styles.imageCover}>
{useImageBackground && <div className={'image-cover-background'} />}
<Image
className={'image-cover-img'}
imageFit={ImageFit.centerContain}
src={appliedImageCover}
onLoad={onImageLoaded}
onLoadingStateChange={onImageLoading}
/>
</div>
<div css={styles.title}>{title}</div>
<div css={styles.content}>{content}</div>
{moreLinkText && <Link css={styles.moreLink}> {moreLinkText} </Link>}
</div>
</a>
);
};
12 changes: 8 additions & 4 deletions Composer/packages/client/src/pages/home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import formatMessage from 'format-message';
import { Link } from 'office-ui-fabric-react/lib/Link';
import { Image, ImageFit } from 'office-ui-fabric-react/lib/Image';
import { Pivot, PivotItem, PivotLinkSize } from 'office-ui-fabric-react/lib/Pivot';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import { RouteComponentProps } from '@reach/router';
import { navigate } from '@reach/router';
import { useRecoilValue } from 'recoil';
Expand Down Expand Up @@ -141,8 +142,8 @@ const Home: React.FC<RouteComponentProps> = () => {
];
return (
<div css={home.outline}>
<h1 css={home.title}>{formatMessage(`Bot Framework Composer`)}</h1>
<div css={home.page}>
<h1 css={home.title}>{formatMessage(`Bot Framework Composer`)}</h1>
<div css={home.leftPage} role="main">
<div css={home.leftContainer}>
<h2 css={home.recentBotsTitle}>{formatMessage(`Recent Bots`)}</h2>
Expand Down Expand Up @@ -192,7 +193,6 @@ const Home: React.FC<RouteComponentProps> = () => {
href={item.url}
imageCover={item.imageCover}
moreLinkText={item.moreText}
rel="noopener nofollow"
target="_blank"
title={item.title}
/>
Expand All @@ -204,7 +204,12 @@ const Home: React.FC<RouteComponentProps> = () => {
<Pivot aria-label="Videos and articles" linkSize={PivotLinkSize.large}>
{feed.tabs.map((tab, index) => (
<PivotItem key={index} headerText={tab.title}>
<div css={home.rowContainer}>
{tab.viewAllLinkText && (
<Link css={home.tabRowViewMore} href={tab.viewAllLinkUrl} target={'_blank'}>
{tab.viewAllLinkText} <Icon iconName={'OpenInNewWindow'}></Icon>{' '}
</Link>
)}
<div css={home.tabRowContainer}>
{tab.cards.map((card, index) => (
<CardWidget
key={index}
Expand All @@ -213,7 +218,6 @@ const Home: React.FC<RouteComponentProps> = () => {
content={card.description}
href={card.url}
imageCover={card.image}
rel="noopener nofollow"
target="_blank"
title={card.title}
/>
Expand Down
79 changes: 0 additions & 79 deletions Composer/packages/client/src/pages/home/ItemContainer.tsx

This file was deleted.

53 changes: 32 additions & 21 deletions Composer/packages/client/src/pages/home/RecentBotList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import {
CheckboxVisibility,
} from 'office-ui-fabric-react/lib/DetailsList';
import formatMessage from 'format-message';
import { useMemo } from 'react';

import { calculateTimeDiff } from '../../utils/fileUtil';

import { detailListContainer, tableCell, content } from './styles';
import * as home from './styles';

interface RecentBotListProps {
onItemChosen: (file: IObjectWithKey) => void;
Expand All @@ -44,7 +45,7 @@ export function RecentBotList(props: RecentBotListProps): JSX.Element {
data: 'string',
onRender: (item) => {
return (
<div data-is-focusable css={tableCell}>
<div data-is-focusable css={home.tableCell}>
<Link
aria-label={formatMessage(`Bot name is {botName}`, { botName: item.name })}
onClick={() => onItemChosen(item)}
Expand All @@ -66,10 +67,10 @@ export function RecentBotList(props: RecentBotListProps): JSX.Element {
data: 'string',
onRender: (item) => {
return (
<div data-is-focusable css={tableCell}>
<div data-is-focusable css={home.tableCell}>
<div
aria-label={formatMessage(`location is {location}`, { location: item.path })}
css={content}
css={home.content}
tabIndex={-1}
>
{item.path}
Expand All @@ -89,10 +90,10 @@ export function RecentBotList(props: RecentBotListProps): JSX.Element {
data: 'number',
onRender: (item) => {
return (
<div data-is-focusable css={tableCell}>
<div data-is-focusable css={home.tableCell}>
<div
aria-label={formatMessage(`Last modified time is {time}`, { time: calculateTimeDiff(item.dateModified) })}
css={content}
css={home.content}
tabIndex={-1}
>
{calculateTimeDiff(item.dateModified)}
Expand All @@ -115,22 +116,32 @@ export function RecentBotList(props: RecentBotListProps): JSX.Element {
);
}

const botList = useMemo(() => {
return (
<DetailsList
isHeaderVisible
checkboxVisibility={CheckboxVisibility.hidden}
columns={tableColums}
compact={false}
getKey={(item) => `${item.path}/${item.name}`}
items={recentProjects}
layoutMode={DetailsListLayoutMode.justified}
selectionMode={SelectionMode.single}
onItemInvoked={onItemChosen}
onRenderDetailsHeader={onRenderDetailsHeader}
/>
);
}, []);

return (
<div css={detailListContainer} data-is-scrollable="true">
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
<DetailsList
isHeaderVisible
checkboxVisibility={CheckboxVisibility.hidden}
columns={tableColums}
compact={false}
getKey={(item) => `${item.path}/${item.name}`}
items={recentProjects}
layoutMode={DetailsListLayoutMode.justified}
selectionMode={SelectionMode.single}
onItemInvoked={onItemChosen}
onRenderDetailsHeader={onRenderDetailsHeader}
/>
</ScrollablePane>
<div css={home.detailListContainer} data-is-scrollable="true">
{recentProjects.length > 5 ? (
<div css={home.detailListScrollWrapper}>
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>{botList}</ScrollablePane>{' '}
</div>
) : (
botList
)}
</div>
);
}
4 changes: 2 additions & 2 deletions Composer/packages/client/src/pages/home/WhatsNewsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ export function WhatsNewsList({ newsList }: WhatsNewsListProps): JSX.Element {
return (
<div aria-label={formatMessage("What's new list")} css={home.whatsNewsContainer} role="region">
<h3 css={home.subtitle}>{formatMessage("What's new")}</h3>
<div css={home.whatsNewsContainer}>
<div css={home.whatsNewsList}>
{newsList.map(({ title, description, url }, index) => {
return (
<Fragment key={index}>
<Link css={home.bluetitle} href={url}>
<Link css={home.bluetitle} href={url} target={'_blank'}>
{title}
</Link>
<p css={home.newsDescription}>{description}</p>
Expand Down
Loading

0 comments on commit b621991

Please sign in to comment.