Skip to content

Commit ca119b5

Browse files
committed
feat: add web-ui to plugin
1 parent 662366f commit ca119b5

File tree

3 files changed

+150
-177
lines changed

3 files changed

+150
-177
lines changed

packages/figma-variables-plugin/src/app/components/App.tsx

+115-68
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
/* eslint-disable @typescript-eslint/no-unsafe-argument */
22
/* eslint-disable @typescript-eslint/no-unsafe-return */
33
/* eslint-disable @typescript-eslint/no-unsafe-call */
4-
import React from 'react';
4+
import React, { useEffect } from 'react';
55
import logo from '../assets/logo.svg';
66
import '@utilitywarehouse/css-reset';
7-
// import '@utilitywarehouse/fontsource';
87
import '../styles/ui.css';
98
import { encodeContent, kebabCase } from '../utils';
10-
import { Heading } from '@utilitywarehouse/web-ui';
9+
import {
10+
Heading,
11+
Button,
12+
CheckboxGroup,
13+
Checkbox,
14+
Box,
15+
Flex,
16+
Alert,
17+
TextField,
18+
} from '@utilitywarehouse/web-ui';
1119

1220
const LoadingSpinner = () => (
1321
<div className="spinner-container">
@@ -28,7 +36,7 @@ function App() {
2836
const repoName = 'design-systems';
2937
const branchName = 'main';
3038
const [selectAll, setSelectAll] = React.useState(false);
31-
const [statusType, setStatusType] = React.useState<'success' | 'error' | ''>('');
39+
const [statusType, setStatusType] = React.useState<'green' | 'red' | 'cyan'>('cyan');
3240

3341
React.useEffect(() => {
3442
// Load saved GitHub token from clientStorage
@@ -38,6 +46,15 @@ function App() {
3846
parent.postMessage({ pluginMessage: { type: 'get-collections' } }, '*');
3947
}, []);
4048

49+
useEffect(() => {
50+
if (statusMessage) {
51+
const timeout = setTimeout(() => {
52+
setStatusMessage('');
53+
}, 5000);
54+
return () => clearTimeout(timeout);
55+
}
56+
}, [statusMessage]);
57+
4158
// Handle messages from the plugin code
4259
window.onmessage = async event => {
4360
const { pluginMessage } = event.data;
@@ -46,11 +63,12 @@ function App() {
4663
if (pluginMessage?.token) {
4764
setTokenLoaded(true);
4865
setStatusMessage('GitHub token loaded.');
66+
setStatusType('green');
4967
}
5068
} else if (pluginMessage.type === 'variables-exported') {
5169
const tokensData = pluginMessage.data;
5270
setStatusMessage('Variables exported. Creating PRs...');
53-
setStatusType('success');
71+
setStatusType('cyan');
5472
await createPullRequests(tokensData);
5573
setExporting(false);
5674
}
@@ -68,8 +86,11 @@ function App() {
6886
const saveToken = () => {
6987
parent.postMessage({ pluginMessage: { type: 'save-token', token: githubToken } }, '*');
7088
setShowTokenInput(false);
89+
if (githubToken) {
90+
setTokenLoaded(true);
91+
}
7192
setStatusMessage('GitHub token saved.');
72-
setStatusType('success');
93+
setStatusType('green');
7394
};
7495

7596
const handleSelectAll = () => {
@@ -82,22 +103,10 @@ function App() {
82103
setSelectAll(!selectAll);
83104
};
84105

85-
const handleCollectionSelection = (collectionKey: string) => {
86-
setSelectedCollections(prevSelected => {
87-
let updatedSelected;
88-
if (prevSelected.includes(collectionKey)) {
89-
updatedSelected = prevSelected.filter(key => key !== collectionKey);
90-
} else {
91-
updatedSelected = [...prevSelected, collectionKey];
92-
}
93-
setSelectAll(updatedSelected.length === collections.length);
94-
return updatedSelected;
95-
});
96-
};
97-
98106
// Export variables and initiate PR creation
99107
const exportVariables = () => {
100108
setExporting(true);
109+
document.body.scrollTop = document.documentElement.scrollTop = 0;
101110
console.log('Exporting with selectedCollections:', selectedCollections);
102111
parent.postMessage(
103112
{
@@ -252,11 +261,11 @@ function App() {
252261
if (!prResponse.ok) throw new Error('Failed to create pull request.');
253262

254263
setStatusMessage('Pull request created successfully.');
255-
setStatusType('success');
264+
setStatusType('green');
256265
} catch (error) {
257266
console.error(error);
258267
setStatusMessage(`Error: ${error.message}`);
259-
setStatusType('error');
268+
setStatusType('red');
260269
} finally {
261270
setExporting(false);
262271
}
@@ -266,73 +275,111 @@ function App() {
266275
setShowTokenInput(true);
267276
};
268277

278+
function resizeWindow(e) {
279+
const size = {
280+
w: Math.max(50, Math.floor(e.clientX + 5)),
281+
h: Math.max(50, Math.floor(e.clientY + 5)),
282+
};
283+
parent.postMessage({ pluginMessage: { type: 'resize', size: size } }, '*');
284+
}
285+
269286
return (
270287
<div>
271288
{tokenLoaded && !showTokenInput && (
272-
<button onClick={editToken} className="edit-token-button">
289+
<Button
290+
onClick={editToken}
291+
className="edit-token-button"
292+
size="small"
293+
sx={{ position: 'absolute', top: 16, right: 16 }}
294+
>
273295
Edit Token
274-
</button>
296+
</Button>
275297
)}
276298
<img src={logo} />
277-
<h2>Export Figma Variables</h2>
299+
<Heading variant="h3" sx={{ my: 2 }}>
300+
Export Figma Variables
301+
</Heading>
278302
{!githubToken && (
279-
<p className="token-message">Enter your GitHub token to export variables and create PRs.</p>
303+
<Alert
304+
colorScheme="cyan"
305+
text="Enter your GitHub token to be able to export the variables and create a PR."
306+
sx={{ mb: 3 }}
307+
/>
308+
)}
309+
{loadingImport && (
310+
<Alert colorScheme="cyan" text="Importing variables, please wait..." sx={{ mb: 3 }} />
280311
)}
281312
{((tokenLoaded && showTokenInput) || !tokenLoaded) && (
282-
<div className="token-wrap">
283-
<label>GitHub Token:</label>
284-
<input
313+
<Box sx={{ padding: 3, backgroundColor: '#fff', borderRadius: '14px', mb: 3 }}>
314+
<TextField
285315
type="password"
316+
label="GitHub Token"
286317
value={githubToken}
287318
onChange={e => setGithubToken(e.target.value)}
288319
/>
289-
<button onClick={saveToken}>Save Token</button>
290-
</div>
320+
<Button onClick={saveToken}>Save Token</Button>
321+
</Box>
291322
)}
323+
{statusMessage && <Alert colorScheme={statusType} text={statusMessage} sx={{ mb: 3 }} />}
292324
{githubToken && (
293-
<div>
294-
<div>
295-
<div className="top-content">
296-
<h3>Select Collections to Export:</h3>
297-
<div>
298-
<input
299-
type="checkbox"
300-
checked={selectAll}
301-
onChange={handleSelectAll}
302-
id="select-all"
303-
/>
304-
<label htmlFor="select-all">Select All</label>
305-
</div>
306-
</div>
307-
{collections.map(collection => (
308-
<div key={collection.key} className="checkbox-group">
309-
<input
310-
type="checkbox"
325+
<Box sx={{ padding: 3, backgroundColor: '#fff', borderRadius: '14px' }}>
326+
<Box mb={2}>
327+
<CheckboxGroup direction="column" label="Select Collections to Export:" sx={{ mb: 3 }}>
328+
<Checkbox
329+
id="select-all"
330+
value="select-all"
331+
label="Select All"
332+
checked={selectAll}
333+
onCheckedChange={handleSelectAll}
334+
/>
335+
</CheckboxGroup>
336+
<CheckboxGroup
337+
direction="row"
338+
wrap="wrap"
339+
value={selectedCollections}
340+
onValueChange={val => setSelectedCollections(val)}
341+
>
342+
{collections.map(collection => (
343+
<Checkbox
344+
key={collection.key}
311345
id={`checkbox-${collection.key}`}
312346
value={collection.key}
313-
checked={selectedCollections.includes(collection.key)}
314-
onChange={() => handleCollectionSelection(collection.key)}
347+
label={collection.name}
348+
helperText={collection.libraryName}
315349
/>
316-
<label htmlFor={`checkbox-${collection.key}`}>
317-
{collection.libraryName} - {collection.name}
318-
</label>
319-
</div>
320-
))}
321-
</div>
322-
{loadingImport && <p>Importing variables, please wait...</p>}
323-
<button
324-
onClick={exportVariables}
325-
disabled={exporting || loadingImport}
326-
className="export"
327-
>
328-
{exporting ? 'Exporting...' : 'Export Variables'}
329-
</button>
330-
</div>
350+
))}
351+
</CheckboxGroup>
352+
</Box>
353+
354+
<Flex direction="column" align={{ mobile: 'stretch', desktop: 'start' }}>
355+
<Button onClick={exportVariables} disabled={exporting || loadingImport} fullWidth>
356+
{exporting ? 'Exporting...' : 'Export Variables'}
357+
</Button>
358+
</Flex>
359+
</Box>
331360
)}
332361
{(exporting || loadingImport) && <LoadingSpinner />}
333-
<p className={statusType ? `status-${statusType}` : '' + ' status-message'}>
334-
{statusMessage}
335-
</p>
362+
363+
<svg
364+
id="corner"
365+
width="16"
366+
height="16"
367+
viewBox="0 0 16 16"
368+
fill="none"
369+
xmlns="http://www.w3.org/2000/svg"
370+
onPointerDown={e => {
371+
e.currentTarget.onpointermove = resizeWindow;
372+
e.currentTarget.setPointerCapture(e.pointerId);
373+
}}
374+
onPointerUp={e => {
375+
e.currentTarget.onpointermove = null;
376+
e.currentTarget.releasePointerCapture(e.pointerId);
377+
}}
378+
>
379+
<path d="M16 0V16H0L16 0Z" fill="white" />
380+
<path d="M6.22577 16H3L16 3V6.22576L6.22577 16Z" fill="#8C8C8C" />
381+
<path d="M11.8602 16H8.63441L16 8.63441V11.8602L11.8602 16Z" fill="#8C8C8C" />
382+
</svg>
336383
</div>
337384
);
338385
}

0 commit comments

Comments
 (0)