Π·Π΅ΡΠΊΠ°Π»ΠΎ ΠΈΠ· https://github.com/microsoft/electionguard-tracking-site.git
π± Add Mobile specific responsive additions
This commit is contained in:
Π ΠΎΠ΄ΠΈΡΠ΅Π»Ρ
e3f33440e7
ΠΠΎΠΌΠΌΠΈΡ
224d1d3b9d
|
@ -14,9 +14,7 @@ const Template: Story<AppBarProps> = (props) => {
|
|||
const theme = useTheme();
|
||||
return (
|
||||
<Stack verticalFill styles={{ root: { backgroundColor: theme.palette.neutralLighterAlt } }}>
|
||||
<AppBar {...props}>
|
||||
<PrimaryButton text="Change Election" />
|
||||
</AppBar>
|
||||
<AppBar {...props} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
@ -26,10 +24,12 @@ StandardAppBar.storyName = 'App Bar';
|
|||
StandardAppBar.args = {
|
||||
logoImageUrl: 'https://themingdesigner.blob.core.windows.net/$web/MicrosoftLogo.png',
|
||||
appName: 'ElectionGuard Ballot Tracker',
|
||||
logoUrl: 'https://www.microsoft.com',
|
||||
};
|
||||
|
||||
export const NoLogoAppBar = Template.bind({});
|
||||
NoLogoAppBar.storyName = 'No Logo App Bar';
|
||||
NoLogoAppBar.args = {
|
||||
export const NoLinkAppBar = Template.bind({});
|
||||
NoLinkAppBar.storyName = 'No Link App Bar';
|
||||
NoLinkAppBar.args = {
|
||||
logoImageUrl: 'https://themingdesigner.blob.core.windows.net/$web/MicrosoftLogo.png',
|
||||
appName: 'ElectionGuard Ballot Tracker',
|
||||
};
|
||||
|
|
|
@ -2,13 +2,38 @@ import React from 'react';
|
|||
import { Image, Stack, Text } from '@fluentui/react';
|
||||
import { Depths } from '@fluentui/theme';
|
||||
import { useTheme } from '@fluentui/react-theme-provider';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
export interface AppBarProps {
|
||||
logoImageUrl?: string;
|
||||
appName: string;
|
||||
export interface MobileAppBarProps {
|
||||
logoImageUrl: string;
|
||||
logoUrl?: string;
|
||||
}
|
||||
|
||||
const AppBar: React.FunctionComponent<AppBarProps> = ({ logoImageUrl, appName, children }) => {
|
||||
const MobileAppBar: React.FunctionComponent<MobileAppBarProps> = ({ logoImageUrl, logoUrl }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
horizontal
|
||||
verticalAlign="center"
|
||||
horizontalAlign="center"
|
||||
styles={{
|
||||
root: {
|
||||
marginBottom: 2,
|
||||
padding: '0px 32px',
|
||||
minHeight: 47,
|
||||
boxShadow: Depths.depth8,
|
||||
backgroundColor: theme.palette.white,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<a href={logoUrl ?? ''}>
|
||||
<Image styles={{ image: { width: '120px', display: 'block' } }} alt="logo" src={logoImageUrl} />
|
||||
</a>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const DesktopAppBar: React.FunctionComponent<AppBarProps> = ({ logoImageUrl, logoUrl, appName, children }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
|
@ -26,18 +51,12 @@ const AppBar: React.FunctionComponent<AppBarProps> = ({ logoImageUrl, appName, c
|
|||
}}
|
||||
>
|
||||
<Stack horizontal verticalAlign="center">
|
||||
{logoImageUrl ? (
|
||||
<>
|
||||
<Image styles={{ image: { width: '120px', display: 'block' } }} alt="logo" src={logoImageUrl} />
|
||||
<Text as="span" styles={{ root: { fontWeight: 600 } }}>
|
||||
{`| ${appName}`}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<Text as="span" styles={{ root: { fontWeight: 600 } }}>
|
||||
{appName}
|
||||
</Text>
|
||||
)}
|
||||
<a href={logoUrl ?? ''}>
|
||||
<Image styles={{ image: { width: '120px', display: 'block' } }} alt="logo" src={logoImageUrl} />
|
||||
</a>
|
||||
<Text as="span" styles={{ root: { fontWeight: 600 } }}>
|
||||
{`| ${appName}`}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack horizontal verticalAlign="center">
|
||||
{children || null}
|
||||
|
@ -46,4 +65,22 @@ const AppBar: React.FunctionComponent<AppBarProps> = ({ logoImageUrl, appName, c
|
|||
);
|
||||
};
|
||||
|
||||
export interface AppBarProps {
|
||||
logoImageUrl: string;
|
||||
logoUrl?: string;
|
||||
appName: string;
|
||||
}
|
||||
|
||||
const AppBar: React.FunctionComponent<AppBarProps> = ({ logoImageUrl, logoUrl, appName, children }) => {
|
||||
const isMobile = useMediaQuery({ query: '(max-width: 480px)' });
|
||||
|
||||
return isMobile ? (
|
||||
<MobileAppBar logoImageUrl={logoImageUrl} logoUrl={logoUrl} />
|
||||
) : (
|
||||
<DesktopAppBar logoImageUrl={logoImageUrl} logoUrl={logoUrl} appName={appName}>
|
||||
{children || null}
|
||||
</DesktopAppBar>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppBar;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import { Text } from '@fluentui/react';
|
||||
import moment from 'moment';
|
||||
import { useTheme } from '@fluentui/react-theme-provider';
|
||||
import Title from './Title';
|
||||
import { useTheme } from '@fluentui/react-theme-provider';
|
||||
|
||||
// TODO #?? Resolve internalization of language using i18n
|
||||
const defaultElectionName = 'Election';
|
||||
|
@ -19,9 +19,9 @@ const ElectionTitle: React.FunctionComponent<ElectionTitleProps> = ({ electionNa
|
|||
const theme = useTheme();
|
||||
return (
|
||||
<Title title={electionName ?? defaultElectionName}>
|
||||
<Text as="span" styles={{ root: { color: theme.palette.neutralSecondary, marginLeft: theme.spacing.l1 } }}>
|
||||
{`${moment(startDate).format(dateFormat)}-${moment(endDate).format(dateFormat)}`}
|
||||
</Text>
|
||||
<Text as="span" styles={{ root: { color: theme.palette.neutralSecondary } }}>{`${moment(startDate).format(
|
||||
dateFormat
|
||||
)}-${moment(endDate).format(dateFormat)}`}</Text>
|
||||
</Title>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,7 +6,6 @@ import LargeCard from './LargeCard';
|
|||
export default {
|
||||
title: 'Components/LargeCard',
|
||||
component: LargeCard,
|
||||
parameters: { layout: 'fullscreen' },
|
||||
} as Meta;
|
||||
|
||||
const Template: Story = () => {
|
||||
|
|
|
@ -2,9 +2,11 @@ import React from 'react';
|
|||
import { useTheme } from '@fluentui/react-theme-provider';
|
||||
import { Card } from '@uifabric/react-cards';
|
||||
|
||||
export interface LargeCardProps {}
|
||||
export interface LargeCardProps {
|
||||
alignToStart?: boolean;
|
||||
}
|
||||
|
||||
const LargeCard: React.FunctionComponent<LargeCardProps> = ({ children }) => {
|
||||
const LargeCard: React.FunctionComponent<LargeCardProps> = ({ alignToStart, children }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Card
|
||||
|
@ -13,6 +15,8 @@ const LargeCard: React.FunctionComponent<LargeCardProps> = ({ children }) => {
|
|||
padding: theme.spacing.l1,
|
||||
backgroundColor: theme.palette.white,
|
||||
marginBottom: theme.spacing.l1,
|
||||
alignItems: alignToStart ? 'flex-start' : 'stretch',
|
||||
height: 'auto',
|
||||
maxWidth: 'auto',
|
||||
},
|
||||
}}
|
||||
|
|
|
@ -2,23 +2,10 @@ import React from 'react';
|
|||
import { Stack } from '@fluentui/react';
|
||||
import AppBar from './AppBar';
|
||||
import { useTheme } from '@fluentui/react-theme-provider';
|
||||
import { Dropdown, IDropdownStyles, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
|
||||
|
||||
// TODO Remove mock data
|
||||
const appName = 'ElectionGuard Ballot Tracker';
|
||||
const logoImageUrl = 'https://themingdesigner.blob.core.windows.net/$web/MicrosoftLogo.png';
|
||||
const placeholder = 'Select Election';
|
||||
const options: IDropdownOption[] = [
|
||||
{ key: '1', text: 'Mock Election 1' },
|
||||
{ key: '2', text: 'Mock Election 2' },
|
||||
{ key: '3', text: 'Mock Election 3' },
|
||||
{ key: '4', text: 'Mock Election 4' },
|
||||
{ key: '5', text: 'Mock Election 5' },
|
||||
];
|
||||
|
||||
const dropdownStyles: Partial<IDropdownStyles> = {
|
||||
dropdown: { width: 300 },
|
||||
};
|
||||
|
||||
export interface LayoutProps {}
|
||||
|
||||
|
@ -26,9 +13,7 @@ const Layout: React.FunctionComponent<LayoutProps> = ({ children }) => {
|
|||
const theme = useTheme();
|
||||
return (
|
||||
<Stack verticalFill>
|
||||
<AppBar appName={appName} logoImageUrl={logoImageUrl}>
|
||||
<Dropdown placeholder={placeholder} ariaLabel={placeholder} options={options} styles={dropdownStyles} />
|
||||
</AppBar>
|
||||
<AppBar appName={appName} logoImageUrl={logoImageUrl} />
|
||||
<Stack
|
||||
verticalFill
|
||||
styles={{
|
||||
|
|
|
@ -2,6 +2,15 @@ import React from 'react';
|
|||
import { Text } from '@fluentui/react';
|
||||
import styled from 'styled-components';
|
||||
import { useTheme } from '@fluentui/react-theme-provider';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
const MobileHeader = styled.header`
|
||||
padding: 16px 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const Header = styled.header`
|
||||
padding: 52px 0px;
|
||||
|
@ -13,11 +22,26 @@ export interface TitleProps {
|
|||
|
||||
const Title: React.FunctionComponent<TitleProps> = ({ title, children }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
const isMobile = useMediaQuery({ query: '(max-width: 480px)' });
|
||||
return isMobile ? (
|
||||
<MobileHeader>
|
||||
<Text variant="xLargePlus" as="h1" styles={{ root: { color: theme.palette.neutralPrimary } }}>
|
||||
{title}
|
||||
</Text>
|
||||
{children || null}
|
||||
</MobileHeader>
|
||||
) : (
|
||||
<Header>
|
||||
<Text variant="xxLargePlus" as="h1" styles={{ root: { color: theme.palette.neutralPrimary } }}>
|
||||
{title}
|
||||
{children || null}
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
marginLeft: theme.spacing.l1,
|
||||
}}
|
||||
>
|
||||
{children || null}
|
||||
</div>
|
||||
</Text>
|
||||
</Header>
|
||||
);
|
||||
|
|
ΠΠ°Π³ΡΡΠ·ΠΊΠ°β¦
Π‘ΡΡΠ»ΠΊΠ° Π² Π½ΠΎΠ²ΠΎΠΉ Π·Π°Π΄Π°ΡΠ΅