mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2024-12-22 13:43:03 -05:00
Refactor UI code so it does not fail when exporting (#137)
This commit is contained in:
parent
7895daaea8
commit
df30dfd376
11 changed files with 231 additions and 156 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -12,7 +12,7 @@
|
||||||
"@create-figma-plugin/ui": "^3.2",
|
"@create-figma-plugin/ui": "^3.2",
|
||||||
"base64-js": "^1.5",
|
"base64-js": "^1.5",
|
||||||
"classnames": "^2.5",
|
"classnames": "^2.5",
|
||||||
"preact": "^10.21",
|
"preact": "^10.22",
|
||||||
"react-hook-form": "^7.51",
|
"react-hook-form": "^7.51",
|
||||||
"romans": "^2.0",
|
"romans": "^2.0",
|
||||||
"slugify": "^1.6",
|
"slugify": "^1.6",
|
||||||
|
@ -6722,9 +6722,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/preact": {
|
"node_modules/preact": {
|
||||||
"version": "10.21.0",
|
"version": "10.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.22.0.tgz",
|
||||||
"integrity": "sha512-aQAIxtzWEwH8ou+OovWVSVNlFImL7xUCwJX3YMqA3U8iKCNC34999fFOnWjYNsylgfPgMexpbk7WYOLtKr/mxg==",
|
"integrity": "sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/preact"
|
"url": "https://opencollective.com/preact"
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
"@create-figma-plugin/ui": "^3.2",
|
"@create-figma-plugin/ui": "^3.2",
|
||||||
"base64-js": "^1.5",
|
"base64-js": "^1.5",
|
||||||
"classnames": "^2.5",
|
"classnames": "^2.5",
|
||||||
"preact": "^10.21",
|
"preact": "^10.22",
|
||||||
"react-hook-form": "^7.51",
|
"react-hook-form": "^7.51",
|
||||||
"romans": "^2.0",
|
"romans": "^2.0",
|
||||||
"slugify": "^1.6",
|
"slugify": "^1.6",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Penpot from '@ui/assets/penpot.svg?react';
|
||||||
import { PenpotExporter } from '@ui/components/PenpotExporter';
|
import { PenpotExporter } from '@ui/components/PenpotExporter';
|
||||||
import { Stack } from '@ui/components/Stack';
|
import { Stack } from '@ui/components/Stack';
|
||||||
import { Wrapper } from '@ui/components/Wrapper';
|
import { Wrapper } from '@ui/components/Wrapper';
|
||||||
|
import { FigmaProvider } from '@ui/context/FigmaContext';
|
||||||
|
|
||||||
// Safe default value to avoid overflowing from the screen
|
// Safe default value to avoid overflowing from the screen
|
||||||
const MAX_HEIGHT = 800;
|
const MAX_HEIGHT = 800;
|
||||||
|
@ -21,18 +22,20 @@ export const App = () => {
|
||||||
}, [height]);
|
}, [height]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper ref={ref} overflowing={(height ?? 0) > MAX_HEIGHT}>
|
<FigmaProvider>
|
||||||
<Stack>
|
<Wrapper ref={ref} overflowing={(height ?? 0) > MAX_HEIGHT}>
|
||||||
<Penpot
|
<Stack>
|
||||||
style={{
|
<Penpot
|
||||||
alignSelf: 'center',
|
style={{
|
||||||
height: 'auto',
|
alignSelf: 'center',
|
||||||
width: '8.125rem',
|
height: 'auto',
|
||||||
fill: 'var(--figma-color-icon)'
|
width: '8.125rem',
|
||||||
}}
|
fill: 'var(--figma-color-icon)'
|
||||||
/>
|
}}
|
||||||
<PenpotExporter />
|
/>
|
||||||
</Stack>
|
<PenpotExporter />
|
||||||
</Wrapper>
|
</Stack>
|
||||||
|
</Wrapper>
|
||||||
|
</FigmaProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
32
ui-src/components/ExportForm.tsx
Normal file
32
ui-src/components/ExportForm.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { Button } from '@create-figma-plugin/ui';
|
||||||
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
|
import { Stack } from '@ui/components/Stack';
|
||||||
|
import { useFigma } from '@ui/context';
|
||||||
|
|
||||||
|
import { MissingFontsSection } from './MissingFontsSection';
|
||||||
|
|
||||||
|
export type FormValues = Record<string, string>;
|
||||||
|
|
||||||
|
export const ExportForm = () => {
|
||||||
|
const { cancel, exportPenpot } = useFigma();
|
||||||
|
const methods = useForm<FormValues>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...methods}>
|
||||||
|
<form onSubmit={methods.handleSubmit(exportPenpot)}>
|
||||||
|
<Stack>
|
||||||
|
<MissingFontsSection />
|
||||||
|
<Stack space="xsmall" direction="row">
|
||||||
|
<Button type="submit" fullWidth>
|
||||||
|
Export to Penpot
|
||||||
|
</Button>
|
||||||
|
<Button secondary onClick={cancel} fullWidth>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,35 +1,11 @@
|
||||||
import { LoadingIndicator } from '@create-figma-plugin/ui';
|
import { LoadingIndicator } from '@create-figma-plugin/ui';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
import { useFigma } from '@ui/context';
|
||||||
|
|
||||||
import { Stack } from './Stack';
|
import { Stack } from './Stack';
|
||||||
|
|
||||||
type ExporterProgressProps = {
|
export const ExporterProgress = () => {
|
||||||
downloading: boolean;
|
const { currentNode, totalPages, processedPages, downloading } = useFigma();
|
||||||
};
|
|
||||||
|
|
||||||
export const ExporterProgress = ({ downloading }: ExporterProgressProps) => {
|
|
||||||
const [currentNode, setCurrentNode] = useState<string | undefined>();
|
|
||||||
const [totalPages, setTotalPages] = useState<number | undefined>();
|
|
||||||
const [processedPages, setProcessedPages] = useState<number | undefined>();
|
|
||||||
|
|
||||||
const onMessage = (event: MessageEvent<{ pluginMessage: { type: string; data: unknown } }>) => {
|
|
||||||
if (event.data.pluginMessage?.type === 'PROGRESS_NODE') {
|
|
||||||
setCurrentNode(event.data.pluginMessage.data as string);
|
|
||||||
} else if (event.data.pluginMessage?.type === 'PROGRESS_TOTAL_PAGES') {
|
|
||||||
setTotalPages(event.data.pluginMessage.data as number);
|
|
||||||
setProcessedPages(0);
|
|
||||||
} else if (event.data.pluginMessage?.type === 'PROGRESS_PROCESSED_PAGES') {
|
|
||||||
setProcessedPages(event.data.pluginMessage.data as number);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('message', onMessage);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('message', onMessage);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const truncateText = (text: string, maxChars: number) => {
|
const truncateText = (text: string, maxChars: number) => {
|
||||||
if (text.length <= maxChars) {
|
if (text.length <= maxChars) {
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import { Banner, IconInfo32, Link, Textbox } from '@create-figma-plugin/ui';
|
import { Banner, IconInfo32, Link, Textbox } from '@create-figma-plugin/ui';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
|
import { useFigma } from '@ui/context';
|
||||||
|
|
||||||
import { Stack } from './Stack';
|
import { Stack } from './Stack';
|
||||||
|
|
||||||
type MissingFontsSectionProps = {
|
export const MissingFontsSection = () => {
|
||||||
fonts?: string[];
|
const { missingFonts } = useFigma();
|
||||||
};
|
|
||||||
|
|
||||||
export const MissingFontsSection = ({ fonts }: MissingFontsSectionProps) => {
|
if (!missingFonts || !missingFonts.length) return null;
|
||||||
if (!fonts || !fonts.length) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack space="small">
|
<Stack space="small">
|
||||||
<Stack space="xsmall">
|
<Stack space="xsmall">
|
||||||
<Banner icon={<IconInfo32 />}>
|
<Banner icon={<IconInfo32 />}>
|
||||||
{fonts.length} custom font{fonts.length > 1 ? 's' : ''} detected
|
{missingFonts.length} custom font{missingFonts.length > 1 ? 's' : ''} detected
|
||||||
</Banner>
|
</Banner>
|
||||||
<span>To export your file with custom fonts, please follow these steps:</span>
|
<span>To export your file with custom fonts, please follow these steps:</span>
|
||||||
<Stack as="ol" space="xsmall" style={{ paddingLeft: '1rem' }}>
|
<Stack as="ol" space="xsmall" style={{ paddingLeft: '1rem' }}>
|
||||||
|
@ -43,7 +43,7 @@ export const MissingFontsSection = ({ fonts }: MissingFontsSectionProps) => {
|
||||||
<li>Return here and paste the font IDs in the section below</li>
|
<li>Return here and paste the font IDs in the section below</li>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
{fonts.map(font => (
|
{missingFonts.map(font => (
|
||||||
<Stack space="2xsmall" key={font}>
|
<Stack space="2xsmall" key={font}>
|
||||||
<ControlledTextbox name={font} placeholder="Paste font ID from Penpot" />
|
<ControlledTextbox name={font} placeholder="Paste font ID from Penpot" />
|
||||||
<span>{font}</span>
|
<span>{font}</span>
|
||||||
|
|
|
@ -1,114 +1,19 @@
|
||||||
import { Banner, Button, IconInfo32, LoadingIndicator } from '@create-figma-plugin/ui';
|
import { LoadingIndicator } from '@create-figma-plugin/ui';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
|
||||||
|
|
||||||
import { Stack } from '@ui/components/Stack';
|
import { useFigma } from '@ui/context';
|
||||||
import { parse } from '@ui/parser';
|
|
||||||
import { PenpotDocument } from '@ui/types';
|
|
||||||
|
|
||||||
|
import { ExportForm } from './ExportForm';
|
||||||
import { ExporterProgress } from './ExporterProgress';
|
import { ExporterProgress } from './ExporterProgress';
|
||||||
import { MissingFontsSection } from './MissingFontsSection';
|
import { PluginReload } from './PluginReload';
|
||||||
|
|
||||||
type FormValues = Record<string, string>;
|
|
||||||
|
|
||||||
export const PenpotExporter = () => {
|
export const PenpotExporter = () => {
|
||||||
const [missingFonts, setMissingFonts] = useState<string[]>();
|
const { loading, needsReload, exporting } = useFigma();
|
||||||
const [needsReload, setNeedsReload] = useState(false);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [exporting, setExporting] = useState(false);
|
|
||||||
const [downloading, setDownloading] = useState(false);
|
|
||||||
const methods = useForm<FormValues>();
|
|
||||||
|
|
||||||
methods.getValues();
|
|
||||||
|
|
||||||
const onMessage = (event: MessageEvent<{ pluginMessage: { type: string; data: unknown } }>) => {
|
|
||||||
if (event.data.pluginMessage?.type == 'PENPOT_DOCUMENT') {
|
|
||||||
setDownloading(true);
|
|
||||||
|
|
||||||
const document = event.data.pluginMessage.data as PenpotDocument;
|
|
||||||
const file = parse(document);
|
|
||||||
|
|
||||||
file.export();
|
|
||||||
} else if (event.data.pluginMessage?.type == 'CUSTOM_FONTS') {
|
|
||||||
setMissingFonts(event.data.pluginMessage.data as string[]);
|
|
||||||
setLoading(false);
|
|
||||||
setNeedsReload(false);
|
|
||||||
} else if (event.data.pluginMessage?.type == 'CHANGES_DETECTED') {
|
|
||||||
setNeedsReload(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const exportPenpot = (data: FormValues) => {
|
|
||||||
setExporting(true);
|
|
||||||
|
|
||||||
parent.postMessage(
|
|
||||||
{
|
|
||||||
pluginMessage: {
|
|
||||||
type: 'export',
|
|
||||||
data
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'*'
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancel = () => {
|
|
||||||
parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*');
|
|
||||||
};
|
|
||||||
|
|
||||||
const reload = () => {
|
|
||||||
setLoading(true);
|
|
||||||
parent.postMessage({ pluginMessage: { type: 'reload' } }, '*');
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('message', onMessage);
|
|
||||||
|
|
||||||
parent.postMessage({ pluginMessage: { type: 'ready' } }, '*');
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('message', onMessage);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (loading) return <LoadingIndicator />;
|
if (loading) return <LoadingIndicator />;
|
||||||
|
|
||||||
if (exporting) return <ExporterProgress downloading={downloading} />;
|
if (exporting) return <ExporterProgress />;
|
||||||
|
|
||||||
if (needsReload) {
|
if (needsReload) return <PluginReload />;
|
||||||
return (
|
|
||||||
<Stack space="small">
|
|
||||||
<Banner icon={<IconInfo32 />}>
|
|
||||||
Changes detected. Please reload the plug-in to ensure all modifications are included in
|
|
||||||
the exported file.
|
|
||||||
</Banner>
|
|
||||||
<Stack space="xsmall" direction="row">
|
|
||||||
<Button onClick={reload} fullWidth>
|
|
||||||
Reload
|
|
||||||
</Button>
|
|
||||||
<Button secondary onClick={cancel} fullWidth>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return <ExportForm />;
|
||||||
<FormProvider {...methods}>
|
|
||||||
<form onSubmit={methods.handleSubmit(exportPenpot)}>
|
|
||||||
<Stack>
|
|
||||||
<MissingFontsSection fonts={missingFonts} />
|
|
||||||
<Stack space="xsmall" direction="row">
|
|
||||||
<Button type="submit" fullWidth>
|
|
||||||
Export to Penpot
|
|
||||||
</Button>
|
|
||||||
<Button secondary onClick={cancel} fullWidth>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</form>
|
|
||||||
</FormProvider>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
25
ui-src/components/PluginReload.tsx
Normal file
25
ui-src/components/PluginReload.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { Banner, Button, IconInfo32 } from '@create-figma-plugin/ui';
|
||||||
|
|
||||||
|
import { Stack } from '@ui/components/Stack';
|
||||||
|
import { useFigma } from '@ui/context';
|
||||||
|
|
||||||
|
export const PluginReload = () => {
|
||||||
|
const { reload, cancel } = useFigma();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack space="small">
|
||||||
|
<Banner icon={<IconInfo32 />}>
|
||||||
|
Changes detected. Please reload the plug-in to ensure all modifications are included in the
|
||||||
|
exported file.
|
||||||
|
</Banner>
|
||||||
|
<Stack space="xsmall" direction="row">
|
||||||
|
<Button onClick={reload} fullWidth>
|
||||||
|
Reload
|
||||||
|
</Button>
|
||||||
|
<Button secondary onClick={cancel} fullWidth>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
113
ui-src/context/FigmaContext.tsx
Normal file
113
ui-src/context/FigmaContext.tsx
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import { JSX, PropsWithChildren, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { FormValues } from '@ui/components/ExportForm';
|
||||||
|
import { parse } from '@ui/parser';
|
||||||
|
import { PenpotDocument } from '@ui/types';
|
||||||
|
|
||||||
|
import { createGenericContext } from './createGenericContext';
|
||||||
|
|
||||||
|
type Context = {
|
||||||
|
missingFonts: string[] | undefined;
|
||||||
|
needsReload: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
exporting: boolean;
|
||||||
|
downloading: boolean;
|
||||||
|
currentNode: string | undefined;
|
||||||
|
totalPages: number | undefined;
|
||||||
|
processedPages: number | undefined;
|
||||||
|
reload: () => void;
|
||||||
|
cancel: () => void;
|
||||||
|
exportPenpot: (data: FormValues) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [useFigma, StateContextProvider] = createGenericContext<Context>();
|
||||||
|
|
||||||
|
const FigmaProvider = ({ children }: PropsWithChildren): JSX.Element => {
|
||||||
|
const [missingFonts, setMissingFonts] = useState<string[]>();
|
||||||
|
const [needsReload, setNeedsReload] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [exporting, setExporting] = useState(false);
|
||||||
|
const [downloading, setDownloading] = useState(false);
|
||||||
|
const [currentNode, setCurrentNode] = useState<string | undefined>();
|
||||||
|
const [totalPages, setTotalPages] = useState<number | undefined>();
|
||||||
|
const [processedPages, setProcessedPages] = useState<number | undefined>();
|
||||||
|
|
||||||
|
const onMessage = (event: MessageEvent<{ pluginMessage: { type: string; data: unknown } }>) => {
|
||||||
|
if (event.data.pluginMessage?.type == 'PENPOT_DOCUMENT') {
|
||||||
|
setDownloading(true);
|
||||||
|
|
||||||
|
const document = event.data.pluginMessage.data as PenpotDocument;
|
||||||
|
const file = parse(document);
|
||||||
|
|
||||||
|
file.export();
|
||||||
|
} else if (event.data.pluginMessage?.type == 'CUSTOM_FONTS') {
|
||||||
|
setMissingFonts(event.data.pluginMessage.data as string[]);
|
||||||
|
setLoading(false);
|
||||||
|
setNeedsReload(false);
|
||||||
|
} else if (event.data.pluginMessage?.type == 'CHANGES_DETECTED') {
|
||||||
|
setNeedsReload(true);
|
||||||
|
} else if (event.data.pluginMessage?.type === 'PROGRESS_NODE') {
|
||||||
|
setCurrentNode(event.data.pluginMessage.data as string);
|
||||||
|
} else if (event.data.pluginMessage?.type === 'PROGRESS_TOTAL_PAGES') {
|
||||||
|
setTotalPages(event.data.pluginMessage.data as number);
|
||||||
|
setProcessedPages(0);
|
||||||
|
} else if (event.data.pluginMessage?.type === 'PROGRESS_PROCESSED_PAGES') {
|
||||||
|
setProcessedPages(event.data.pluginMessage.data as number);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const reload = () => {
|
||||||
|
setLoading(true);
|
||||||
|
parent.postMessage({ pluginMessage: { type: 'reload' } }, '*');
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*');
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportPenpot = (data: FormValues) => {
|
||||||
|
setExporting(true);
|
||||||
|
|
||||||
|
parent.postMessage(
|
||||||
|
{
|
||||||
|
pluginMessage: {
|
||||||
|
type: 'export',
|
||||||
|
data
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'*'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('message', onMessage);
|
||||||
|
|
||||||
|
parent.postMessage({ pluginMessage: { type: 'ready' } }, '*');
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('message', onMessage);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StateContextProvider
|
||||||
|
value={{
|
||||||
|
missingFonts,
|
||||||
|
needsReload,
|
||||||
|
loading,
|
||||||
|
exporting,
|
||||||
|
downloading,
|
||||||
|
currentNode,
|
||||||
|
totalPages,
|
||||||
|
processedPages,
|
||||||
|
reload,
|
||||||
|
cancel,
|
||||||
|
exportPenpot
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</StateContextProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { FigmaProvider, useFigma };
|
19
ui-src/context/createGenericContext.ts
Normal file
19
ui-src/context/createGenericContext.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { Provider } from 'preact';
|
||||||
|
// @TODO: Try to use react
|
||||||
|
import { createContext, useContext } from 'react';
|
||||||
|
|
||||||
|
export const createGenericContext = <T>(): [<K extends T>() => K, Provider<T | undefined>] => {
|
||||||
|
const genericContext = createContext<T | undefined>(undefined);
|
||||||
|
|
||||||
|
const useGenericContext = <K extends T>(): K => {
|
||||||
|
const context = useContext(genericContext);
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useGenericContext must be used within a Provider');
|
||||||
|
}
|
||||||
|
|
||||||
|
return context as K;
|
||||||
|
};
|
||||||
|
|
||||||
|
return [useGenericContext, genericContext.Provider];
|
||||||
|
};
|
2
ui-src/context/index.ts
Normal file
2
ui-src/context/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './createGenericContext';
|
||||||
|
export * from './FigmaContext';
|
Loading…
Reference in a new issue