Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(PPDSC-2555): SVG Exporter - Export with CSS vars #493

Merged
merged 17 commits into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from 7 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

Large diffs are not rendered by default.

151 changes: 151 additions & 0 deletions site/components/svg-previewer/controls/download-controls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/* eslint-disable no-useless-concat */
import React, {useRef} from 'react';
import {saveAs} from 'file-saver';
import JSZip from 'jszip';
import {Button, Select, SelectOption} from 'newskit';
import {StyledSingleSVGDownloadButton} from '../styled';
import {DownloadControlsProps} from './types';

export const DownloadControls = ({
hexesObj,
baseSvgCodeGroup,
svgCodeGroup,
}: DownloadControlsProps) => {
const selectSvgEl = useRef<HTMLSelectElement>(null);
const [selectedIndexSvg, setSelectedIndexSvg] = React.useState('0');

const buildSvgFileName = (svgName: string) =>
svgName.replaceAll(' ', '-').toLowerCase();

const replaceColorHashWithToken = (svgCode: string) => {
let svgCodeCopy = svgCode;

Object.entries(hexesObj).forEach(hex => {
const name = `var(--color-${hex[1]})`;
svgCodeCopy = svgCodeCopy.replaceAll(hex[0].toUpperCase(), name);
});

return svgCodeCopy;
};

const replaceFigmaIdWithVariable = (figmaSvg: string) => {
let figmaSvgCopy = figmaSvg;
const idTypeList = ['mask', 'filter', 'clip'];
const escapeRegex = (string: string) =>
// eslint-disable-next-line no-useless-escape
string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');

idTypeList.forEach(idType => {
for (let i = 0; figmaSvgCopy.includes(`id="${idType}${i}"`); ++i) {
figmaSvgCopy = figmaSvgCopy.replace(
new RegExp(`id="${idType}${i}"`, 'g'),
`id={${idType}${i}}`,
);
}
});

for (let i = 0; figmaSvgCopy.includes(`mask="url(#mask${i})"`); ++i) {
const escapedRegex = escapeRegex(`mask="url(#mask${i})"`);
figmaSvgCopy = figmaSvgCopy.replace(
new RegExp(escapedRegex, 'g'),
'mask={`url(#${mask' + `${i}` + '})`}',
);
}

for (let i = 0; figmaSvgCopy.includes(`clip-path="url(#clip${i})"`); ++i) {
const escapedRegex = escapeRegex(`clip-path="url(#clip${i})"`);
figmaSvgCopy = figmaSvgCopy.replace(
new RegExp(escapedRegex, 'g'),
'clipPath={`url(#${clip' + `${i}` + '})`}',
);
}

for (let i = 0; figmaSvgCopy.includes(`filter="url(#filter${i})"`); ++i) {
const escapedRegex = escapeRegex(`filter="url(#filter${i})"`);
figmaSvgCopy = figmaSvgCopy.replace(
new RegExp(escapedRegex, 'g'),
'filter={`url(#${filter' + `${i}` + '})`}',
);
}

return figmaSvgCopy;
};

const buildAndDownloadSingleSvgFile = () => {
const selectElementValue = selectSvgEl.current?.value;
const selectedSvg = selectElementValue
? parseInt(selectElementValue, 10)
: undefined;

if (selectedSvg !== undefined && baseSvgCodeGroup) {
let svgCode: string;
svgCode = replaceFigmaIdWithVariable(
baseSvgCodeGroup[selectedSvg].figmaSvg,
);
svgCode = replaceColorHashWithToken(svgCode);

const svgFileName = buildSvgFileName(baseSvgCodeGroup[selectedSvg].name);

const fileBlob = new Blob([svgCode], {
type: 'text/plain;charset=utf-8;',
});
saveAs(fileBlob, `${svgFileName}.svg`);
} else {
// eslint-disable-next-line no-console
console.error('Please select an SVG from the dropdown');
}
};

const buildAndDownloadAllSvgFile = () => {
const zip = new JSZip();

baseSvgCodeGroup?.forEach(svg => {
let figmaSvgCopy = svg.figmaSvg;
figmaSvgCopy = replaceFigmaIdWithVariable(figmaSvgCopy);
figmaSvgCopy = replaceColorHashWithToken(figmaSvgCopy);

const svgFileName = buildSvgFileName(svg.name);
zip.file(`${svgFileName}.svg`, figmaSvgCopy);
});

zip.generateAsync({type: 'blob'}).then(content => {
saveAs(content, 'All_NK_SVGs');
});
};

// Handle single SVGs download
const handleDownloadButtonClick = () => {
buildAndDownloadSingleSvgFile();
};

return (
<>
{baseSvgCodeGroup && (
<Select
/* @ts-ignore nextline */
ref={selectSvgEl}
disabled={!svgCodeGroup}
value={selectedIndexSvg}
onChange={e => setSelectedIndexSvg(e.target.value)}
>
{baseSvgCodeGroup &&
baseSvgCodeGroup.map((svg, index) => (
<SelectOption key={svg.name} value={`${index}`}>
{svg.name.replace(' ', '')} .svg
</SelectOption>
))}
</Select>
)}

<StyledSingleSVGDownloadButton
data-testid="single-svg-button"
overrides={{stylePreset: 'buttonMinimalPrimary'}}
onClick={handleDownloadButtonClick}
>
Download .svg
</StyledSingleSVGDownloadButton>

<Button onClick={buildAndDownloadAllSvgFile}>Download all .zip</Button>
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import React, {useState} from 'react';
import {Button, getColorCssFromTheme, styled, Theme} from 'newskit';
import {
getColorCssFromTheme,
Theme,
Select,
SelectOption,
Switch,
toNewsKitIcon,
} from 'newskit';
import {DarkMode, LightMode} from '@emotion-icons/material';
import {themeList, ThemeNames} from '../colors-theme-list';
import {ThemeControlsProps} from './types';

export const IconFilledDarkMode = toNewsKitIcon(DarkMode);
export const IconFilledLightMode = toNewsKitIcon(LightMode);

export const ThemeControls = ({
hexesObj,
getThemeFromList,
Expand Down Expand Up @@ -71,37 +82,33 @@ export const ThemeControls = ({
setCurrentThemeName(newSelectedThemeName);
};

const SwitchThemeButton = () => {
const StyledSwitchThemeButton = styled(Button)`
margin: 50px 20px 30px 20px;
`;
return (
<StyledSwitchThemeButton
disabled={!svgCodeGroup}
onClick={handleSwitchThemeButtonClick}
>
SWITCH THEME: {isLightTheme ? 'LIGHT' : 'DARK'}
</StyledSwitchThemeButton>
);
};

return (
<>
<select
<Select
data-testid="select-theme-element"
/* @ts-ignore nexline */
onChange={handleThemeSelection}
disabled={!svgCodeGroup}
style={{height: '26px', fontSize: '16px'}}
overrides={{button: {maxWidth: '196px'}}}
value={currentThemeName}
>
{themeList.map(theme => (
<option key={theme.name} value={theme.name}>
<SelectOption key={theme.name} value={theme.name}>
{theme.name}
</option>
</SelectOption>
))}
</select>

<SwitchThemeButton />
</Select>
<Switch
checked={isLightTheme}
onClick={handleSwitchThemeButtonClick}
size="medium"
overrides={{
onIcon: IconFilledLightMode,
offIcon: IconFilledDarkMode,
}}
label="Toggle Theme"
tabIndex={-1}
/>
</>
);
};
34 changes: 34 additions & 0 deletions site/components/svg-previewer/styled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {Button, styled, GridLayout, getColorCssFromTheme} from 'newskit';

export const StyledSingleSVGDownloadButton = styled(Button)`
margin-right: 20px;
`;

export const StyledSvgGroupContainer = styled(GridLayout)``;

export const StyledSvgPreviewerContainer = styled(GridLayout)`
/*
display: flex;
flex-direction: column;
align-items: center;
*/
mutebg marked this conversation as resolved.
Show resolved Hide resolved
`;

export const StyledButtonsContainer = styled(GridLayout)`
position: fixed;
top: var(--headerSize);
margin-top: -12px;
${getColorCssFromTheme('background', 'interface020')}
${getColorCssFromTheme('borderBottomColor', 'interface040')}
border-bottom-width: 1px;
border-bottom-style: solid;
z-index: 1000;
`;

export const StyledSingleSvgWrapper = styled.div`
padding: 0 10vw;
svg {
max-width: 80vw;
max-height: 80vh;
}
`;
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import React, {useEffect, useState} from 'react';
import {getColorCssFromTheme, getSSRId, P} from 'newskit';
import {
getColorCssFromTheme,
getSSRId,
Scroll,
P,
ScrollSnapAlignment,
GridLayout,
} from 'newskit';
import dompurify from 'dompurify';
import {themeList, ThemeNames} from './colors-theme-list';
import {
StyledButtonsContainer,
StyledSingleSvgWrapper,
StyledSvgGroupContainer,
StyledSvgPreviewerContainer,
} from './styled';
import {DownloadControls} from './controls/download-controls';
Expand Down Expand Up @@ -155,35 +161,72 @@ export const SvgPreviewer: React.FC = () => {

return (
<StyledSvgPreviewerContainer>
<StyledButtonsContainer>
<ThemeControls
setSvgCodeGroup={setSvgCodeGroup}
hexesObj={hexesObj}
baseSvgCodeGroup={baseSvgCodeGroup}
svgCodeGroup={svgCodeGroup}
getThemeFromList={getThemeFromList}
setIds={setIds}
currentThemeName={currentThemeName}
setCurrentThemeName={setCurrentThemeName}
/>

<DownloadControls
hexesObj={hexesObj}
baseSvgCodeGroup={baseSvgCodeGroup}
svgCodeGroup={svgCodeGroup}
/>
<StyledButtonsContainer
rows={{xs: 'sizing080'}}
alignItems="center"
justifyContent="sapce-between"
columns="1fr 1fr"
overrides={{
paddingInline: '10%',
paddingBlock: '8px',
minWidth: '100%',
}}
>
<GridLayout
columns="1fr 1fr"
alignItems="center"
rows={{xs: 'sizing080'}}
>
<ThemeControls
setSvgCodeGroup={setSvgCodeGroup}
hexesObj={hexesObj}
baseSvgCodeGroup={baseSvgCodeGroup}
svgCodeGroup={svgCodeGroup}
getThemeFromList={getThemeFromList}
setIds={setIds}
currentThemeName={currentThemeName}
setCurrentThemeName={setCurrentThemeName}
/>
</GridLayout>
<GridLayout
columns="1fr 1fr 1fr"
alignItems="baseline"
rows={{xs: 'sizing080'}}
>
<DownloadControls
hexesObj={hexesObj}
baseSvgCodeGroup={baseSvgCodeGroup}
svgCodeGroup={svgCodeGroup}
/>
</GridLayout>
</StyledButtonsContainer>

{svgCodeGroup && baseSvgCodeGroup && (
<StyledSvgGroupContainer>
{svgCodeGroup.map((svgCode, index) => (
<StyledSingleSvgWrapper key={baseSvgCodeGroup[index].name}>
<P>{baseSvgCodeGroup[index].name}</P>
{/* eslint-disable-next-line react/no-danger */}
<div dangerouslySetInnerHTML={{__html: sanitizer(svgCode)}} />
</StyledSingleSvgWrapper>
))}
</StyledSvgGroupContainer>
<Scroll
id="scroll"
snapAlign="center"
controls="static"
stepDistance={window.innerWidth}
>
<GridLayout
mutebg marked this conversation as resolved.
Show resolved Hide resolved
autoRows="1fr"
autoFlow="column dense"
overrides={{
marginBlock: '10vh',
maxHeight: '80vh',
}}
>
{svgCodeGroup.map((svgCode, index) => (
<ScrollSnapAlignment key={getSSRId()}>
mutebg marked this conversation as resolved.
Show resolved Hide resolved
<StyledSingleSvgWrapper key={baseSvgCodeGroup[index].name}>
mutebg marked this conversation as resolved.
Show resolved Hide resolved
<P>{baseSvgCodeGroup[index].name}</P>
{/* eslint-disable-next-line react/no-danger */}
<div dangerouslySetInnerHTML={{__html: sanitizer(svgCode)}} />
</StyledSingleSvgWrapper>
</ScrollSnapAlignment>
))}
</GridLayout>
</Scroll>
)}
</StyledSvgPreviewerContainer>
);
Expand Down
Loading