Feature: Toggle Page Size between ISO A4 and US Letter

This commit is contained in:
Amruth Pillai
2022-10-15 00:54:59 +02:00
parent 136e143e12
commit f0f552a635
38 changed files with 903 additions and 783 deletions
@@ -15,7 +15,7 @@
}
&.break::after {
content: 'A4 Page Break';
content: 'Page Break';
top: calc(297mm - 19px);
@apply absolute w-full border-b border-dashed border-neutral-800/75;
@@ -28,6 +28,15 @@
}
}
&.format-letter {
width: 216mm;
min-height: 279mm;
&.break::after {
top: calc(279mm - 19px);
}
}
.markdown {
ul {
padding-left: 1.5em;
+5 -3
View File
@@ -1,5 +1,5 @@
import { css } from '@emotion/css';
import { CustomCSS, Theme, Typography } from '@reactive-resume/schema';
import { CustomCSS, PageConfig, ThemeConfig, Typography } from '@reactive-resume/schema';
import clsx from 'clsx';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
@@ -23,9 +23,10 @@ const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
const resume = useAppSelector((state) => state.resume.present);
const breakLine: boolean = useAppSelector((state) => state.build.page.breakLine);
const theme: Theme = get(resume, 'metadata.theme');
const theme: ThemeConfig = get(resume, 'metadata.theme');
const customCSS: CustomCSS = get(resume, 'metadata.css');
const template: string = get(resume, 'metadata.template');
const pageConfig: PageConfig = get(resume, 'metadata.page');
const typography: Typography = get(resume, 'metadata.typography');
const themeCSS = useMemo(() => !isEmpty(theme) && generateThemeStyles(theme), [theme]);
@@ -33,7 +34,7 @@ const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
const TemplatePage: React.FC<PageProps> | null = useMemo(() => templateMap[template].component, [template]);
return (
<div data-page={page + 1} className={styles.container}>
<div data-page={page + 1} data-format={pageConfig.format || 'A4'} className={styles.container}>
<div
className={clsx({
reset: true,
@@ -42,6 +43,7 @@ const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
[css(themeCSS)]: true,
[css(typographyCSS)]: true,
[css(customCSS.value)]: customCSS.visible,
[styles['format-letter']]: pageConfig?.format === 'Letter',
})}
>
{TemplatePage && <TemplatePage page={page} />}
@@ -10,7 +10,7 @@ import {
Switch,
TextField,
} from '@mui/material';
import { DateConfig, Resume } from '@reactive-resume/schema';
import { DateConfig, PageConfig, Resume } from '@reactive-resume/schema';
import dayjs from 'dayjs';
import get from 'lodash/get';
import { useRouter } from 'next/router';
@@ -47,10 +47,11 @@ const Settings = () => {
const id: number = useMemo(() => get(resume, 'id'), [resume]);
const slug: string = useMemo(() => get(resume, 'slug'), [resume]);
const username: string = useMemo(() => get(resume, 'user.username'), [resume]);
const pageConfig: PageConfig = useMemo(() => get(resume, 'metadata.page'), [resume]);
const dateConfig: DateConfig = useMemo(() => get(resume, 'metadata.date'), [resume]);
const isDarkMode = useMemo(() => theme === 'dark', [theme]);
const exampleString = useMemo(() => `Eg. ${dayjs().utc().format(dateConfig.format)}`, [dateConfig.format]);
const exampleDateString = useMemo(() => `Eg. ${dayjs().utc().format(dateConfig.format)}`, [dateConfig.format]);
const themeString = useMemo(() => (isDarkMode ? 'Matte Black Everything' : 'As bright as your future'), [isDarkMode]);
const { mutateAsync: loadSampleDataMutation } = useMutation<Resume, ServerError, LoadSampleDataParams>(
@@ -60,6 +61,9 @@ const Settings = () => {
const handleSetTheme = (value: boolean) => dispatch(setTheme({ theme: value ? 'dark' : 'light' }));
const handleChangePageFormat = (value: PageConfig['format'] | null) =>
dispatch(setResumeState({ path: 'metadata.page.format', value }));
const handleChangeDateFormat = (value: string | null) =>
dispatch(setResumeState({ path: 'metadata.date.format', value }));
@@ -118,13 +122,13 @@ const Settings = () => {
primary={t<string>('builder.rightSidebar.sections.settings.global.date.primary')}
secondary={t<string>('builder.rightSidebar.sections.settings.global.date.secondary')}
/>
<Autocomplete<string, false, boolean, false>
<Autocomplete<string, false, true, false>
disableClearable
className="my-2 w-full"
options={dateFormatOptions}
value={dateConfig.format}
onChange={(_, value) => handleChangeDateFormat(value)}
renderInput={(params) => <TextField {...params} helperText={exampleString} />}
renderInput={(params) => <TextField {...params} helperText={exampleDateString} />}
/>
</ListItem>
@@ -134,7 +138,7 @@ const Settings = () => {
primary={t<string>('builder.rightSidebar.sections.settings.global.language.primary')}
secondary={t<string>('builder.rightSidebar.sections.settings.global.language.secondary')}
/>
<Autocomplete<Language, false, boolean, false>
<Autocomplete<Language, false, true, false>
disableClearable
className="my-2 w-full"
options={languages}
@@ -159,6 +163,23 @@ const Settings = () => {
{t<string>('builder.rightSidebar.sections.settings.page.heading')}
</ListSubheader>
<ListItem className="flex-col">
<ListItemText
className="w-full"
primary={t<string>('builder.rightSidebar.sections.settings.page.format.primary')}
secondary={t<string>('builder.rightSidebar.sections.settings.page.format.secondary')}
/>
<Autocomplete<PageConfig['format'], false, true, false>
disableClearable
defaultValue="A4"
className="my-2 w-full"
options={['A4', 'Letter']}
value={pageConfig?.format || 'A4'}
renderInput={(params) => <TextField {...params} />}
onChange={(_, value) => handleChangePageFormat(value)}
/>
</ListItem>
<ListItem>
<ListItemText
primary={t<string>('builder.rightSidebar.sections.settings.page.orientation.primary')}
@@ -1,4 +1,4 @@
import { Theme as ThemeType } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import get from 'lodash/get';
import { useTranslation } from 'next-i18next';
@@ -16,7 +16,7 @@ const Theme = () => {
const dispatch = useAppDispatch();
const { background, text, primary } = useAppSelector<ThemeType>((state) =>
const { background, text, primary } = useAppSelector<ThemeConfig>((state) =>
get(state.resume.present, 'metadata.theme')
);
+11 -11
View File
@@ -13,22 +13,22 @@
"@emotion/css": "^11.10.0",
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@hello-pangea/dnd": "^16.0.0",
"@hello-pangea/dnd": "^16.0.1",
"@hookform/resolvers": "2.9.8",
"@monaco-editor/react": "^4.4.6",
"@mui/icons-material": "^5.10.6",
"@mui/lab": "^5.0.0-alpha.102",
"@mui/material": "^5.10.8",
"@mui/system": "^5.10.8",
"@mui/icons-material": "^5.10.9",
"@mui/lab": "^5.0.0-alpha.103",
"@mui/material": "^5.10.9",
"@mui/system": "^5.10.9",
"@mui/x-date-pickers": "5.0.4",
"@next/env": "^12.3.1",
"@react-oauth/google": "^0.2.8",
"@reduxjs/toolkit": "^1.8.5",
"axios": "^1.1.0",
"@reduxjs/toolkit": "^1.8.6",
"axios": "^1.1.2",
"clsx": "^1.2.1",
"dayjs": "^1.11.5",
"downloadjs": "^1.4.7",
"joi": "^17.6.2",
"joi": "^17.6.3",
"lodash": "^4.17.21",
"md5-hex": "^4.0.0",
"monaco-editor": "^0.34.0",
@@ -64,7 +64,7 @@
"@tailwindcss/typography": "^0.5.7",
"@types/downloadjs": "^1.4.3",
"@types/lodash": "^4.14.186",
"@types/node": "^18.8.3",
"@types/node": "^18.8.5",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"@types/react-redux": "^7.1.24",
@@ -75,8 +75,8 @@
"csstype": "^3.1.1",
"eslint-config-next": "^12.3.1",
"eslint-plugin-tailwindcss": "^3.6.2",
"next-sitemap": "^3.1.23",
"postcss": "^8.4.17",
"next-sitemap": "^3.1.25",
"postcss": "^8.4.18",
"sass": "^1.55.0",
"tailwindcss": "^3.1.8",
"typescript": "^4.8.4"
+1 -1
View File
@@ -6,7 +6,7 @@
}
},
"footer": {
"credit": "Vášnivý projekt <1>Amrutha Pillaie</1>",
"credit": "Vášnivý projekt <1>Amruth Pillai</1>",
"license": "Od komunity, pro komunitu."
},
"markdown": {
+4
View File
@@ -290,6 +290,10 @@
},
"heading": "Settings",
"page": {
"format": {
"primary": "Paper Size",
"secondary": "Determines the dimensions of your resume pages"
},
"break-line": {
"primary": "Break Line",
"secondary": "Show a line on all pages to mark the height of an A4 page"
+1 -1
View File
@@ -6,7 +6,7 @@
}
},
"footer": {
"credit": "<1>Amruth Pillai szenvedélyes projektje</1>",
"credit": "<1>Amruth Pillai</1> szenvedélyes projektje",
"license": "A közösség által, a közösségért."
},
"markdown": {
+1 -1
View File
@@ -6,7 +6,7 @@
}
},
"footer": {
"credit": "គម្រោងបង្កើតឡើងដោយលោក <1> Amruth Pillai</1>",
"credit": "គម្រោងបង្កើតឡើងដោយលោក <1>Amruth Pillai</1>",
"license": "ដោយសហគមន៍ ដើម្បីសហគមន៍។"
},
"markdown": {
+1 -1
View File
@@ -6,7 +6,7 @@
}
},
"footer": {
"credit": "<1>Amruth Pillai의 열정 프로젝트</1>",
"credit": "<1>Amruth Pillai의</1> 열정 프로젝트",
"license": "커뮤니티에 의한, 커뮤니티를 위한."
},
"markdown": {
+1 -1
View File
@@ -6,7 +6,7 @@
}
},
"footer": {
"credit": "<1>अमृत पिल्लई यांचा एक उत्कट प्रकल्प</1>",
"credit": "<1>अमृत पिल्लई</1> यांचा एक उत्कट प्रकल्प",
"license": "समाजाने, समाजासाठी."
},
"markdown": {
+1 -1
View File
@@ -6,7 +6,7 @@
}
},
"footer": {
"credit": "<1> अमृत पिल्लै द्वारा एक जोश परियोजना</1>",
"credit": "<1>अमृत पिल्लै</1> द्वारा एक जोश परियोजना",
"license": "समुदाय द्वारा, समुदाय को लागी।"
},
"markdown": {
+1 -1
View File
@@ -6,7 +6,7 @@
}
},
"footer": {
"credit": "<1> ଅମୃତ ପିଲ୍ଲାଇ</1> ଦ୍ୱାରା ଏକ ଉତ୍ସାହ ପ୍ରୋଜେକ୍ଟ ।",
"credit": "<1>ଅମୃତ ପିଲ୍ଲାଇ</1> ଦ୍ୱାରା ଏକ ଉତ୍ସାହ ପ୍ରୋଜେକ୍ଟ ।",
"license": "ସମ୍ପ୍ରଦାୟ ଦ୍ୱାରା, ସମ୍ପ୍ରଦାୟ ପାଇଁ ।"
},
"markdown": {
+1 -1
View File
@@ -6,7 +6,7 @@
}
},
"footer": {
"credit": "Một dự án làm với đam mê của <1> Amruth Pillai</1>",
"credit": "Một dự án làm với đam mê của <1>Amruth Pillai</1>",
"license": "Vì cộng đồng, cho cộng đồng."
},
"markdown": {
+2 -2
View File
@@ -1,5 +1,5 @@
import { css } from '@emotion/css';
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import clsx from 'clsx';
import get from 'lodash/get';
import { useMemo } from 'react';
@@ -17,7 +17,7 @@ const Castform: React.FC<PageProps> = ({ page }) => {
const isFirstPage = useMemo(() => page === 0, [page]);
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
@@ -1,12 +1,12 @@
import { darken } from '@mui/material';
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import get from 'lodash/get';
import { useMemo } from 'react';
import { useAppSelector } from '@/store/hooks';
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const darkerPrimary = useMemo(() => darken(theme.primary, 0.2), [theme.primary]);
return (
@@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import clsx from 'clsx';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
@@ -19,7 +19,7 @@ export const MastheadSidebar: React.FC = () => {
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
(state) => state.resume.present.basics
);
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
+2 -2
View File
@@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import { alpha } from '@mui/material';
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import clsx from 'clsx';
import get from 'lodash/get';
import { useMemo } from 'react';
@@ -18,7 +18,7 @@ const Gengar: React.FC<PageProps> = ({ page }) => {
const isFirstPage = useMemo(() => page === 0, [page]);
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
const backgroundColor: string = useMemo(() => alpha(theme.primary, 0.15), [theme.primary]);
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
+2 -2
View File
@@ -1,10 +1,10 @@
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import get from 'lodash/get';
import { useAppSelector } from '@/store/hooks';
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
return (
<h3
+2 -2
View File
@@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
import { alpha } from '@mui/material';
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import clsx from 'clsx';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
@@ -20,7 +20,7 @@ export const MastheadSidebar: React.FC = () => {
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
(state) => state.resume.present.basics
);
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
const iconColor = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
@@ -1,5 +1,5 @@
import { alpha } from '@mui/material';
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import get from 'lodash/get';
import isArray from 'lodash/isArray';
import isEmpty from 'lodash/isEmpty';
@@ -13,7 +13,7 @@ type Props = {
};
const BadgeDisplay: React.FC<Props> = ({ items }) => {
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
if (!isArray(items) || isEmpty(items)) return null;
+2 -2
View File
@@ -1,10 +1,10 @@
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import get from 'lodash/get';
import { useAppSelector } from '@/store/hooks';
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
return (
<h3
@@ -1,4 +1,4 @@
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import get from 'lodash/get';
import isArray from 'lodash/isArray';
import isEmpty from 'lodash/isEmpty';
@@ -12,7 +12,7 @@ type Props = {
};
const BadgeDisplay: React.FC<Props> = ({ items }) => {
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
if (!isArray(items) || isEmpty(items)) return null;
+2 -2
View File
@@ -1,10 +1,10 @@
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import get from 'lodash/get';
import { useAppSelector } from '@/store/hooks';
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
return (
<h2
@@ -1,6 +1,6 @@
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
import { alpha } from '@mui/material';
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
@@ -16,7 +16,7 @@ const Masthead: React.FC = () => {
const { name, photo, headline, summary, email, phone, birthdate, website, location, profiles } = useAppSelector(
(state) => state.resume.present.basics
);
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
return (
<div>
+2 -2
View File
@@ -1,10 +1,10 @@
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import get from 'lodash/get';
import { useAppSelector } from '@/store/hooks';
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
return (
<h4 className="mb-2 font-bold uppercase" style={{ color: theme.primary }}>
+2 -2
View File
@@ -1,10 +1,10 @@
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import get from 'lodash/get';
import { useAppSelector } from '@/store/hooks';
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
return (
<h3
@@ -1,5 +1,5 @@
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
import { Theme } from '@reactive-resume/schema';
import { ThemeConfig } from '@reactive-resume/schema';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import { useMemo } from 'react';
@@ -62,7 +62,7 @@ export const MastheadSidebar: React.FC = () => {
};
export const MastheadMain: React.FC = () => {
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
const { name, summary, headline } = useAppSelector((state) => state.resume.present.basics);
+3
View File
@@ -0,0 +1,3 @@
export const capitalize = (str: string) => {
return str.charAt(0).toUpperCase() + str.slice(1);
};
+2 -2
View File
@@ -1,4 +1,4 @@
import { Theme, Typography } from '@reactive-resume/schema';
import { ThemeConfig, Typography } from '@reactive-resume/schema';
import { RgbColor } from 'react-colorful';
import { hexColorPattern } from '@/config/colors';
@@ -27,7 +27,7 @@ export const generateTypographyStyles = ({ family, size }: Typography): string =
h6 { font-size: ${size.heading / 3.5}px; line-height: ${size.heading / 3.5}px; }
`;
export const generateThemeStyles = ({ text, background, primary }: Theme): string => `
export const generateThemeStyles = ({ text, background, primary }: ThemeConfig): string => `
color: ${text};
background-color: ${background};
--primary-color: ${primary};