@figma-vars/hooks 3.0.0-beta.1 โ 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +417 -173
- package/dist/api/fetcher.d.ts +32 -1
- package/dist/api/fetcher.d.ts.map +1 -1
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/mutator.d.ts +36 -9
- package/dist/api/mutator.d.ts.map +1 -1
- package/dist/constants/index.d.ts +2 -28
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/contexts/FigmaTokenContext.d.ts.map +1 -1
- package/dist/contexts/FigmaVarsProvider.d.ts +3 -1
- package/dist/contexts/FigmaVarsProvider.d.ts.map +1 -1
- package/dist/contexts/index.d.ts +1 -1
- package/dist/contexts/index.d.ts.map +1 -1
- package/dist/contexts/useFigmaTokenContext.d.ts.map +1 -1
- package/dist/core/index.d.cts +8 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core.cjs +1 -0
- package/dist/core.d.cts +2 -0
- package/dist/core.d.ts +2 -0
- package/dist/core.mjs +20 -0
- package/dist/hooks/index.d.ts +64 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useBulkUpdateVariables.d.ts.map +1 -1
- package/dist/hooks/useCollectionById.d.ts +31 -0
- package/dist/hooks/useCollectionById.d.ts.map +1 -0
- package/dist/hooks/useCreateVariable.d.ts.map +1 -1
- package/dist/hooks/useDeleteVariable.d.ts.map +1 -1
- package/dist/hooks/useFigmaToken.d.ts.map +1 -1
- package/dist/hooks/useInvalidateVariables.d.ts +34 -0
- package/dist/hooks/useInvalidateVariables.d.ts.map +1 -0
- package/dist/hooks/useModesByCollection.d.ts +34 -0
- package/dist/hooks/useModesByCollection.d.ts.map +1 -0
- package/dist/hooks/usePublishedVariables.d.ts +42 -0
- package/dist/hooks/usePublishedVariables.d.ts.map +1 -0
- package/dist/hooks/useUpdateVariable.d.ts.map +1 -1
- package/dist/hooks/useVariableById.d.ts +31 -0
- package/dist/hooks/useVariableById.d.ts.map +1 -0
- package/dist/hooks/useVariableCollections.d.ts.map +1 -1
- package/dist/hooks/useVariableModes.d.ts.map +1 -1
- package/dist/hooks/useVariables.d.ts +3 -1
- package/dist/hooks/useVariables.d.ts.map +1 -1
- package/dist/index-5ZyKWuYv.cjs +1 -0
- package/dist/index-ClHLYVvu.js +142 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +9 -4
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +319 -163
- package/dist/types/contexts.d.ts +30 -3
- package/dist/types/contexts.d.ts.map +1 -1
- package/dist/types/figma.d.ts +64 -3
- package/dist/types/figma.d.ts.map +1 -1
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/mutations.d.ts +18 -4
- package/dist/types/mutations.d.ts.map +1 -1
- package/dist/utils/errorHelpers.d.ts +142 -0
- package/dist/utils/errorHelpers.d.ts.map +1 -0
- package/dist/utils/filterVariables.d.ts.map +1 -1
- package/dist/utils/index.d.ts +3 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/swrKeys.d.ts +24 -0
- package/dist/utils/swrKeys.d.ts.map +1 -0
- package/dist/utils/typeGuards.d.ts +50 -0
- package/dist/utils/typeGuards.d.ts.map +1 -0
- package/package.json +75 -31
- package/scripts/export-variables.mjs +101 -0
- package/dist/index.d.mts +0 -2
package/README.md
CHANGED
|
@@ -1,278 +1,522 @@
|
|
|
1
|
+
# FigmaVars/hooks
|
|
2
|
+
|
|
1
3
|
<p align="left">
|
|
2
4
|
<img src="assets/figma-vars-tagline-light.png" alt="FigmaVars Logo" width="700px" />
|
|
3
5
|
</p>
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Built and maintained by Mark Learst.
|
|
8
|
+
|
|
9
|
+
A fast, typed React 19.2.3 hooks library for the Figma Variables API: fetch, update, and manage design tokens via the official [Figma REST API](https://www.figma.com/developers/api#variables).
|
|
6
10
|
|
|
7
|
-
Built for the modern web, this library provides a suite of hooks to fetch, manage, and mutate your design tokens, making it easy to sync them between Figma and your React applications, Storybooks, or design system dashboards.
|
|
11
|
+
Built for the modern web, this library provides a suite of hooks to fetch, manage, and mutate your design tokens/variables, making it easy to sync them between Figma and your React applications, Storybooks, or design system dashboards.
|
|
8
12
|
|
|
9
13
|

|
|
10
|
-

|
|
11
15
|
[](https://codecov.io/gh/marklearst/figma-vars-hooks)
|
|
12
16
|

|
|
13
17
|

|
|
14
18
|

|
|
15
19
|

|
|
16
|
-

|
|
17
|
-

|
|
18
|
-

|
|
19
|
-
|
|
20
|
-
---
|
|
21
20
|
|
|
22
|
-
##
|
|
21
|
+
## ๐ Why 3.0
|
|
23
22
|
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
23
|
+
- โจ **New DX Features**: SWR configuration support, error handling utilities, cache invalidation helpers
|
|
24
|
+
- ๐ง **React 19.2 Ready**: Optimized hooks with proper cleanup and stable function references
|
|
25
|
+
- ๐ก๏ธ **Better Error Handling**: `FigmaApiError` class with HTTP status codes for better error differentiation
|
|
26
|
+
- โ
**Type Safety**: Removed unsafe type assertions, improved type definitions throughout
|
|
27
|
+
- ๐ **Performance**: Hardened SWR usage (stable keys, `null` to disable, cleaner fallback handling)
|
|
28
|
+
- ๐ฆ **Modern Tooling**: Node 20+ toolchain, strict TypeScript, and ESM-first packaging with CJS interop
|
|
29
|
+
- ๐ฅ๏ธ **CLI Export Tool**: Automate variable exports with `figma-vars-export` for CI/CD (Enterprise required)
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
## ๐งฑ Architecture Highlights
|
|
31
|
+
## ๐ Features at a Glance
|
|
33
32
|
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
---
|
|
33
|
+
- **Modern React 19.2 hooks** for variables, collections, modes, and published variables
|
|
34
|
+
- **Ergonomic mutation hooks** with consistent loading/error states
|
|
35
|
+
- **SWR configuration support** for customizing caching and revalidation behavior
|
|
36
|
+
- **Error handling utilities** for type-safe error checking and status code access
|
|
37
|
+
- **Cache invalidation helpers** for automatic data refresh after mutations
|
|
38
|
+
- **CLI export tool** (`figma-vars-export`) for automating variable exports to JSON (Enterprise required)
|
|
39
|
+
- **Fallback JSON support** (object or string) for offline/static use - works without Enterprise!
|
|
40
|
+
- **Typed core entrypoint** for non-React consumers (Axios, TanStack Query, server scripts)
|
|
41
|
+
- **100% test coverage** + strict TypeScript + clean exports/attw/publint/size-limit checks
|
|
44
42
|
|
|
45
43
|
## ๐ฆ Install
|
|
46
44
|
|
|
47
45
|
```bash
|
|
48
46
|
npm install @figma-vars/hooks
|
|
49
47
|
# or
|
|
50
|
-
yarn add @figma-vars/hooks
|
|
51
|
-
# or
|
|
52
48
|
pnpm add @figma-vars/hooks
|
|
53
49
|
```
|
|
54
50
|
|
|
55
|
-
|
|
51
|
+
Peer deps: `react` and `react-dom`.
|
|
56
52
|
|
|
57
|
-
|
|
53
|
+
## ๐ฅ๏ธ CLI Export Tool
|
|
54
|
+
|
|
55
|
+
The package includes a **CLI tool** (`figma-vars-export`) for automatically exporting Figma variables to JSON via the REST API. Perfect for CI/CD pipelines, build scripts, or one-off exports.
|
|
56
|
+
|
|
57
|
+
> โ ๏ธ **Enterprise Required**: The CLI tool uses the Figma Variables REST API, which requires a **Figma Enterprise account**. Without Enterprise, use the [Dev Mode plugin export](#exporting-variables-for-fallback) method instead.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Using npx (no install needed)
|
|
61
|
+
FIGMA_TOKEN=your_token npx figma-vars-export --file-key YOUR_FILE_KEY --out ./variables.json
|
|
62
|
+
|
|
63
|
+
# After installing
|
|
64
|
+
npm install @figma-vars/hooks
|
|
65
|
+
FIGMA_TOKEN=your_token figma-vars-export --file-key YOUR_FILE_KEY --out ./variables.json
|
|
58
66
|
|
|
59
|
-
|
|
67
|
+
# Show help
|
|
68
|
+
figma-vars-export --help
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Options:**
|
|
72
|
+
|
|
73
|
+
- `--file-key` - Figma file key (required, or set `FIGMA_FILE_KEY` env var)
|
|
74
|
+
- `--out` - Output path (default: `data/figma-variables.json`)
|
|
75
|
+
- `--help` - Show help message
|
|
76
|
+
|
|
77
|
+
**Environment Variables:**
|
|
78
|
+
|
|
79
|
+
- `FIGMA_TOKEN` or `FIGMA_PAT` - Figma Personal Access Token (required)
|
|
80
|
+
- `FIGMA_FILE_KEY` - Figma file key (optional)
|
|
81
|
+
|
|
82
|
+
**Example Output:**
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Saved variables to ./variables.json
|
|
86
|
+
Variables count: 42
|
|
87
|
+
```
|
|
60
88
|
|
|
61
|
-
|
|
89
|
+
**No Enterprise?** See [Exporting variables for fallback](#exporting-variables-for-fallback) for alternative methods that work without Enterprise.
|
|
62
90
|
|
|
63
|
-
|
|
91
|
+
## ๐ ๏ธ Quick Start (SWR-powered hooks)
|
|
64
92
|
|
|
65
93
|
```tsx
|
|
66
|
-
|
|
67
|
-
import React from 'react'
|
|
68
|
-
import ReactDOM from 'react-dom/client'
|
|
69
|
-
import { FigmaVarsProvider } from '@figma-vars/hooks'
|
|
70
|
-
import App from './App'
|
|
94
|
+
import { FigmaVarsProvider, useVariables } from '@figma-vars/hooks'
|
|
71
95
|
|
|
72
|
-
// The token can come from anywhere: .env, localStorage, state, etc.
|
|
73
96
|
const FIGMA_TOKEN = import.meta.env.VITE_FIGMA_TOKEN
|
|
74
|
-
const FIGMA_FILE_KEY = 'your-
|
|
97
|
+
const FIGMA_FILE_KEY = 'your-file-key'
|
|
75
98
|
|
|
76
|
-
|
|
77
|
-
|
|
99
|
+
function App() {
|
|
100
|
+
return (
|
|
78
101
|
<FigmaVarsProvider
|
|
79
102
|
token={FIGMA_TOKEN}
|
|
80
|
-
fileKey={FIGMA_FILE_KEY}
|
|
81
|
-
|
|
103
|
+
fileKey={FIGMA_FILE_KEY}
|
|
104
|
+
swrConfig={{
|
|
105
|
+
revalidateOnFocus: false,
|
|
106
|
+
dedupingInterval: 5000,
|
|
107
|
+
}}>
|
|
108
|
+
<Tokens />
|
|
82
109
|
</FigmaVarsProvider>
|
|
83
|
-
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function Tokens() {
|
|
114
|
+
const { data, isLoading, error } = useVariables()
|
|
115
|
+
if (isLoading) return <div>Loadingโฆ</div>
|
|
116
|
+
if (error) return <div>Error: {error.message}</div>
|
|
117
|
+
const variables = Object.values(data?.meta.variables ?? {})
|
|
118
|
+
return <pre>{JSON.stringify(variables, null, 2)}</pre>
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## ๐งฉ Non-SWR Usage (Core entrypoint)
|
|
123
|
+
|
|
124
|
+
Use the `/core` build when you prefer Axios/TanStack/server scripts without React/SWR.
|
|
125
|
+
|
|
126
|
+
**Axios example (GET + bulk PUT)**
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import axios from 'axios'
|
|
130
|
+
import { FIGMA_FILE_VARIABLES_PATH } from '@figma-vars/hooks/core'
|
|
131
|
+
|
|
132
|
+
const token = process.env.FIGMA_TOKEN!
|
|
133
|
+
const fileKey = process.env.FIGMA_FILE_KEY!
|
|
134
|
+
|
|
135
|
+
// Fetch local variables
|
|
136
|
+
const url = `https://api.figma.com${FIGMA_FILE_VARIABLES_PATH(fileKey)}/local`
|
|
137
|
+
const { data } = await axios.get(url, {
|
|
138
|
+
headers: { 'X-FIGMA-TOKEN': token, 'Content-Type': 'application/json' },
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Bulk update
|
|
142
|
+
await axios.put(
|
|
143
|
+
`https://api.figma.com${FIGMA_FILE_VARIABLES_PATH(fileKey)}`,
|
|
144
|
+
{ variables: [{ action: 'UPDATE', id: 'VariableId:123', name: 'new-name' }] },
|
|
145
|
+
{ headers: { 'X-FIGMA-TOKEN': token, 'Content-Type': 'application/json' } }
|
|
84
146
|
)
|
|
85
147
|
```
|
|
86
148
|
|
|
87
|
-
|
|
149
|
+
**TanStack Query example**
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
153
|
+
import { FIGMA_FILE_VARIABLES_PATH, fetcher, mutator } from '@figma-vars/hooks/core'
|
|
154
|
+
|
|
155
|
+
const token = process.env.FIGMA_TOKEN!
|
|
156
|
+
const fileKey = process.env.FIGMA_FILE_KEY!
|
|
88
157
|
|
|
89
|
-
|
|
158
|
+
export function useLocalVariables() {
|
|
159
|
+
return useQuery({
|
|
160
|
+
queryKey: ['figma-local', fileKey],
|
|
161
|
+
queryFn: () => fetcher(`${FIGMA_FILE_VARIABLES_PATH(fileKey)}/local`, token),
|
|
162
|
+
staleTime: 60_000,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function useBulkUpdate() {
|
|
167
|
+
const qc = useQueryClient()
|
|
168
|
+
return useMutation({
|
|
169
|
+
mutationFn: (payload: unknown) =>
|
|
170
|
+
mutator(FIGMA_FILE_VARIABLES_PATH(fileKey), token, 'UPDATE', payload),
|
|
171
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['figma-local', fileKey] }),
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## ๐ก๏ธ Fallback JSON (offline/static)
|
|
177
|
+
|
|
178
|
+
Pass `fallbackFile` (object or JSON string) to `FigmaVarsProvider` to bypass live API calls:
|
|
90
179
|
|
|
91
180
|
```tsx
|
|
92
|
-
|
|
93
|
-
|
|
181
|
+
import exportedVariables from './figma-variables.json'
|
|
182
|
+
;<FigmaVarsProvider
|
|
183
|
+
token={null}
|
|
184
|
+
fileKey={null}
|
|
185
|
+
fallbackFile={exportedVariables}>
|
|
186
|
+
<App />
|
|
187
|
+
</FigmaVarsProvider>
|
|
188
|
+
```
|
|
94
189
|
|
|
95
|
-
|
|
96
|
-
const { data, isLoading, error } = useVariables()
|
|
190
|
+
### Exporting variables for fallback
|
|
97
191
|
|
|
98
|
-
|
|
99
|
-
|
|
192
|
+
There are several ways to get your Figma variables as JSON:
|
|
193
|
+
|
|
194
|
+
1. **Dev Mode / plugin export (recommended, no Enterprise needed)** โญ
|
|
195
|
+
- Use a Variables exporter plugin in Figma Dev Mode to download the full Variables panel as JSON
|
|
196
|
+
- Save anywhere (e.g., `data/figma-variables.json`) and pass it to `fallbackFile`
|
|
197
|
+
- Works for everyone, no Enterprise account required!
|
|
198
|
+
|
|
199
|
+
2. **CLI export tool (Enterprise required)** ๐
|
|
200
|
+
- Automatically exports via REST API - perfect for CI/CD and automation
|
|
201
|
+
- See the [CLI Export Tool](#-cli-export-tool) section above for full usage details
|
|
202
|
+
- Also available from cloned repo: `node scripts/export-variables.mjs --file-key KEY --out file.json`
|
|
203
|
+
|
|
204
|
+
- **Desktop MCP (manual/partial)**: Selecting a frame and running `get_variable_defs` returns only that selectionโs variables. Use plugin/REST exports for complete coverage.
|
|
205
|
+
|
|
206
|
+
4. **Style Dictionary**
|
|
207
|
+
- Once you have the JSON (from any path), feed it into Style Dictionary to emit platform-specific artifacts
|
|
208
|
+
- Or import it directly via `fallbackFile`
|
|
209
|
+
|
|
210
|
+
## ๐ง Mutation Hooks (verbs fixed)
|
|
211
|
+
|
|
212
|
+
- `useCreateVariable` โ POST via bulk endpoint with `action: 'CREATE'`
|
|
213
|
+
- `useUpdateVariable` โ PUT via bulk endpoint with `action: 'UPDATE'`
|
|
214
|
+
- `useDeleteVariable` โ DELETE via bulk endpoint with `action: 'DELETE'`
|
|
215
|
+
- `useBulkUpdateVariables` โ PUT bulk payload (collections, modes, variables, values)
|
|
216
|
+
|
|
217
|
+
All return `{ mutate, data, error, isLoading, isSuccess, isError }`.
|
|
218
|
+
|
|
219
|
+
### Example: Creating and updating variables
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
import { useCreateVariable, useUpdateVariable, useInvalidateVariables } from '@figma-vars/hooks'
|
|
223
|
+
|
|
224
|
+
function VariableEditor() {
|
|
225
|
+
const { mutate: create } = useCreateVariable()
|
|
226
|
+
const { mutate: update } = useUpdateVariable()
|
|
227
|
+
const { invalidate } = useInvalidateVariables()
|
|
228
|
+
|
|
229
|
+
const handleCreate = async () => {
|
|
230
|
+
await create({
|
|
231
|
+
name: 'Primary Color',
|
|
232
|
+
variableCollectionId: 'CollectionId:123',
|
|
233
|
+
resolvedType: 'COLOR',
|
|
234
|
+
})
|
|
235
|
+
invalidate() // Refresh cache after mutation
|
|
236
|
+
}
|
|
100
237
|
|
|
101
|
-
|
|
102
|
-
|
|
238
|
+
const handleUpdate = async (id: string) => {
|
|
239
|
+
await update({
|
|
240
|
+
variableId: id,
|
|
241
|
+
payload: { name: 'Updated Name' },
|
|
242
|
+
})
|
|
243
|
+
invalidate() // Refresh cache after mutation
|
|
244
|
+
}
|
|
103
245
|
|
|
104
246
|
return (
|
|
105
|
-
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
</li>
|
|
110
|
-
))}
|
|
111
|
-
</ul>
|
|
247
|
+
<>
|
|
248
|
+
<button onClick={handleCreate}>Create Variable</button>
|
|
249
|
+
<button onClick={() => handleUpdate('VariableId:123')}>Update</button>
|
|
250
|
+
</>
|
|
112
251
|
)
|
|
113
252
|
}
|
|
114
253
|
```
|
|
115
254
|
|
|
116
|
-
|
|
255
|
+
## ๐ก๏ธ Error Handling
|
|
117
256
|
|
|
118
|
-
|
|
257
|
+
### Error Boundaries (Recommended)
|
|
119
258
|
|
|
120
|
-
|
|
259
|
+
Wrap your Figma-connected components with an error boundary to gracefully handle errors:
|
|
121
260
|
|
|
122
261
|
```tsx
|
|
123
|
-
|
|
124
|
-
import {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
form.elements.namedItem('variableName') as HTMLInputElement
|
|
135
|
-
)?.value
|
|
136
|
-
|
|
137
|
-
if (!variableName) return
|
|
138
|
-
|
|
139
|
-
const payload: CreateVariablePayload = {
|
|
140
|
-
name: variableName,
|
|
141
|
-
variableCollectionId: collectionId,
|
|
142
|
-
resolvedType: 'COLOR', // Example type
|
|
143
|
-
}
|
|
144
|
-
mutate(payload)
|
|
145
|
-
}
|
|
262
|
+
import { ErrorBoundary } from 'react-error-boundary'
|
|
263
|
+
import { FigmaVarsProvider } from '@figma-vars/hooks'
|
|
264
|
+
|
|
265
|
+
function FigmaErrorFallback({ error }: { error: Error }) {
|
|
266
|
+
return (
|
|
267
|
+
<div role='alert'>
|
|
268
|
+
<h2>Failed to load Figma data</h2>
|
|
269
|
+
<pre>{error.message}</pre>
|
|
270
|
+
</div>
|
|
271
|
+
)
|
|
272
|
+
}
|
|
146
273
|
|
|
274
|
+
function App() {
|
|
147
275
|
return (
|
|
148
|
-
<
|
|
149
|
-
<
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
disabled={isLoading}>
|
|
156
|
-
{isLoading ? 'Creating...' : 'Create Variable'}
|
|
157
|
-
</button>
|
|
158
|
-
{error && <p>Error: {error.message}</p>}
|
|
159
|
-
{data && <p>Created variable with ID: {data.id}</p>}
|
|
160
|
-
</form>
|
|
276
|
+
<ErrorBoundary FallbackComponent={FigmaErrorFallback}>
|
|
277
|
+
<FigmaVarsProvider
|
|
278
|
+
token={FIGMA_TOKEN}
|
|
279
|
+
fileKey={FIGMA_FILE_KEY}>
|
|
280
|
+
<YourApp />
|
|
281
|
+
</FigmaVarsProvider>
|
|
282
|
+
</ErrorBoundary>
|
|
161
283
|
)
|
|
162
284
|
}
|
|
163
285
|
```
|
|
164
286
|
|
|
165
|
-
|
|
287
|
+
> **Note:** The provider validates fallback file structure at runtime and logs warnings in development. Invalid fallback data won't crash the app but will result in `undefined` data.
|
|
288
|
+
|
|
289
|
+
### Runtime Validation
|
|
166
290
|
|
|
167
|
-
|
|
291
|
+
Use type guards to validate data at runtime:
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
import { isLocalVariablesResponse, isPublishedVariablesResponse } from '@figma-vars/hooks'
|
|
168
295
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
296
|
+
// Validate before using
|
|
297
|
+
if (isLocalVariablesResponse(data)) {
|
|
298
|
+
// Safe to access data.meta.variables
|
|
299
|
+
}
|
|
300
|
+
```
|
|
173
301
|
|
|
174
|
-
###
|
|
302
|
+
### Error Utilities
|
|
175
303
|
|
|
176
|
-
|
|
304
|
+
3.0.0 introduces powerful error handling utilities for type-safe error checking:
|
|
177
305
|
|
|
178
306
|
```tsx
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const { token, fileKey } = useFigmaToken()
|
|
184
|
-
|
|
185
|
-
// Use the token and file key to make custom API requests
|
|
186
|
-
const apiRequest = async () => {
|
|
187
|
-
const response = await fetch(
|
|
188
|
-
`https://api.figma.com/v1/files/${fileKey}/variables`,
|
|
189
|
-
{
|
|
190
|
-
headers: {
|
|
191
|
-
'X-Figma-Token': token,
|
|
192
|
-
},
|
|
193
|
-
}
|
|
194
|
-
)
|
|
307
|
+
import { isFigmaApiError, getErrorStatus, getErrorMessage, hasErrorStatus } from '@figma-vars/hooks'
|
|
308
|
+
|
|
309
|
+
function ErrorHandler({ error }: { error: Error | null }) {
|
|
310
|
+
if (!error) return null
|
|
195
311
|
|
|
196
|
-
|
|
197
|
-
|
|
312
|
+
// Type guard for FigmaApiError
|
|
313
|
+
if (isFigmaApiError(error)) {
|
|
314
|
+
const status = error.statusCode
|
|
315
|
+
|
|
316
|
+
if (status === 401) {
|
|
317
|
+
return <div>Authentication required. Please check your token.</div>
|
|
318
|
+
}
|
|
319
|
+
if (status === 403) {
|
|
320
|
+
return <div>Access forbidden. Check file permissions.</div>
|
|
321
|
+
}
|
|
322
|
+
if (status === 429) {
|
|
323
|
+
return <div>Rate limit exceeded. Please wait before retrying.</div>
|
|
324
|
+
}
|
|
325
|
+
if (status === 404) {
|
|
326
|
+
return <div>File or variable not found.</div>
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Helper functions
|
|
331
|
+
const status = getErrorStatus(error) // number | null
|
|
332
|
+
const message = getErrorMessage(error) // string
|
|
333
|
+
|
|
334
|
+
// Convenience check
|
|
335
|
+
if (hasErrorStatus(error, 401)) {
|
|
336
|
+
// Handle unauthorized
|
|
198
337
|
}
|
|
199
338
|
|
|
200
|
-
return <
|
|
339
|
+
return <div>Error: {message}</div>
|
|
201
340
|
}
|
|
202
341
|
```
|
|
203
342
|
|
|
204
|
-
|
|
343
|
+
**Common HTTP Status Codes:**
|
|
205
344
|
|
|
206
|
-
|
|
345
|
+
- `401` - Unauthorized (invalid or missing token)
|
|
346
|
+
- `403` - Forbidden (insufficient permissions)
|
|
347
|
+
- `404` - Not Found (file/variable doesn't exist)
|
|
348
|
+
- `429` - Too Many Requests (rate limit exceeded)
|
|
349
|
+
|
|
350
|
+
## ๐ Cache Management
|
|
351
|
+
|
|
352
|
+
After mutations, use `useInvalidateVariables` to refresh cached data:
|
|
207
353
|
|
|
208
354
|
```tsx
|
|
209
|
-
|
|
210
|
-
import { useVariables } from '@figma-vars/hooks'
|
|
355
|
+
import { useUpdateVariable, useInvalidateVariables } from '@figma-vars/hooks'
|
|
211
356
|
|
|
212
|
-
function
|
|
213
|
-
const {
|
|
357
|
+
function UpdateButton({ variableId }: { variableId: string }) {
|
|
358
|
+
const { mutate, isLoading } = useUpdateVariable()
|
|
359
|
+
const { invalidate, revalidate } = useInvalidateVariables()
|
|
360
|
+
|
|
361
|
+
const handleUpdate = async () => {
|
|
362
|
+
await mutate({
|
|
363
|
+
variableId,
|
|
364
|
+
payload: { name: 'New Name' },
|
|
365
|
+
})
|
|
214
366
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
</div>
|
|
221
|
-
)
|
|
367
|
+
// Option 1: Invalidate (refetch on next access)
|
|
368
|
+
invalidate()
|
|
369
|
+
|
|
370
|
+
// Option 2: Revalidate immediately (refetch now)
|
|
371
|
+
// revalidate()
|
|
222
372
|
}
|
|
223
373
|
|
|
224
|
-
|
|
374
|
+
return (
|
|
375
|
+
<button
|
|
376
|
+
onClick={handleUpdate}
|
|
377
|
+
disabled={isLoading}>
|
|
378
|
+
{isLoading ? 'Updating...' : 'Update Variable'}
|
|
379
|
+
</button>
|
|
380
|
+
)
|
|
225
381
|
}
|
|
226
382
|
```
|
|
227
383
|
|
|
228
|
-
|
|
384
|
+
## โ๏ธ SWR Configuration
|
|
229
385
|
|
|
230
|
-
|
|
386
|
+
Customize SWR behavior globally through the provider:
|
|
231
387
|
|
|
232
|
-
|
|
388
|
+
```tsx
|
|
389
|
+
<FigmaVarsProvider
|
|
390
|
+
token={FIGMA_TOKEN}
|
|
391
|
+
fileKey={FIGMA_FILE_KEY}
|
|
392
|
+
swrConfig={{
|
|
393
|
+
revalidateOnFocus: false, // Don't refetch on window focus
|
|
394
|
+
dedupingInterval: 5000, // Dedupe requests within 5s
|
|
395
|
+
errorRetryCount: 3, // Retry failed requests 3 times
|
|
396
|
+
errorRetryInterval: 1000, // Wait 1s between retries
|
|
397
|
+
onError: error => {
|
|
398
|
+
// Global error handler
|
|
399
|
+
if (isFigmaApiError(error) && error.statusCode === 429) {
|
|
400
|
+
console.warn('Rate limited, backing off...')
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
}}>
|
|
404
|
+
<App />
|
|
405
|
+
</FigmaVarsProvider>
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Common SWR Options:**
|
|
233
409
|
|
|
234
|
-
- `
|
|
235
|
-
- `
|
|
236
|
-
- `
|
|
237
|
-
- `
|
|
410
|
+
- `revalidateOnFocus` - Refetch when window regains focus (default: `true`)
|
|
411
|
+
- `dedupingInterval` - Deduplication interval in ms (default: `2000`)
|
|
412
|
+
- `errorRetryCount` - Max retry attempts (default: `5`)
|
|
413
|
+
- `refreshInterval` - Polling interval in ms (default: `0` = disabled)
|
|
414
|
+
- `onError` - Global error callback
|
|
238
415
|
|
|
239
|
-
|
|
416
|
+
## ๐ API Cheat Sheet
|
|
240
417
|
|
|
241
|
-
|
|
418
|
+
### Hooks
|
|
242
419
|
|
|
243
|
-
- `
|
|
244
|
-
-
|
|
245
|
-
- `
|
|
246
|
-
-
|
|
420
|
+
- **Queries**: `useVariables` (local), `usePublishedVariables` (library/published), `useVariableCollections`, `useVariableModes`, `useFigmaToken`
|
|
421
|
+
- **Granular Selectors**: `useCollectionById`, `useModesByCollection`, `useVariableById` (optimized selectors for specific entities)
|
|
422
|
+
- **Mutations**: `useCreateVariable`, `useUpdateVariable`, `useDeleteVariable`, `useBulkUpdateVariables`
|
|
423
|
+
- **Cache**: `useInvalidateVariables` (invalidate/revalidate cache)
|
|
424
|
+
|
|
425
|
+
### Utilities
|
|
426
|
+
|
|
427
|
+
- **Filtering**: `filterVariables` (filter by type, name, etc.)
|
|
428
|
+
- **Error Handling**: `isFigmaApiError`, `getErrorStatus`, `getErrorMessage`, `hasErrorStatus`
|
|
429
|
+
- **Type Guards**: `isLocalVariablesResponse`, `isPublishedVariablesResponse`, `validateFallbackData` (runtime validation)
|
|
430
|
+
- **SWR Keys**: `getVariablesKey`, `getPublishedVariablesKey`, `getInvalidationKeys` (centralized cache key construction)
|
|
431
|
+
- **Core helpers**: `fetcher`, `mutator`, constants for endpoints and headers
|
|
247
432
|
|
|
248
433
|
### Types
|
|
249
434
|
|
|
250
|
-
|
|
435
|
+
- **Responses**: `LocalVariablesResponse`, `PublishedVariablesResponse`
|
|
436
|
+
- **Variables**: `FigmaVariable`, `FigmaCollection`, `VariableMode`
|
|
437
|
+
- **Mutations**: `BulkUpdatePayload`, `CreateVariablePayload`, `UpdateVariablePayload`
|
|
438
|
+
- **Errors**: `FigmaApiError` (extends `Error` with `statusCode`)
|
|
251
439
|
|
|
252
|
-
|
|
440
|
+
## ๐ Auth & Scope
|
|
441
|
+
|
|
442
|
+
- Header: `X-FIGMA-TOKEN: <PAT>`
|
|
443
|
+
- Scopes: `file_variables:read` for GETs, `file_variables:write` for mutations.
|
|
444
|
+
- Enterprise Full seat required for live API; fallback JSON works without a token.
|
|
253
445
|
|
|
254
|
-
##
|
|
446
|
+
## โ ๏ธ Enterprise Requirement and Offline Options
|
|
255
447
|
|
|
256
|
-
The
|
|
448
|
+
- The Figma Variables REST API requires a Figma Enterprise seat for live requests. Without Enterprise, live calls will fail even with a valid PAT.
|
|
449
|
+
- The library remains useful without Enterprise: supply `fallbackFile` (object or JSON string) exported from Figma (Dev Mode plugin, CLI, or Figma MCP server output) and all read hooks work offline/for static deployments.
|
|
450
|
+
- MCP/other exporters: as long as they emit the same JSON shape as the Variables API, you can feed that JSON into `fallbackFile`; mutations still require Enterprise access.
|
|
451
|
+
|
|
452
|
+
## ๐ซ Do Not Publish Tokens or File Keys
|
|
453
|
+
|
|
454
|
+
- Never commit PATs or file keys to git, Storybook static builds, or client bundles.
|
|
455
|
+
- Use environment variables (`process.env` / `import.meta.env`) and secret managers; keep them server-side where possible.
|
|
456
|
+
- Prefer `fallbackFile` with `token={null}`/`fileKey={null}` for demos and public Storybooks.
|
|
457
|
+
- Avoid logging tokens or keys; scrub them from error messages and analytics.
|
|
458
|
+
|
|
459
|
+
## ๐ Rate Limits
|
|
460
|
+
|
|
461
|
+
- Figma enforces per-token limits. Rely on SWR/TanStack caching, avoid unnecessary refetches, and prefer fallback JSON for static sites.
|
|
462
|
+
- Use `swrConfig` to customize `dedupingInterval` and `errorRetryCount` to optimize API usage.
|
|
463
|
+
- Handle `429` rate limit errors with `isFigmaApiError` and implement exponential backoff if needed.
|
|
464
|
+
|
|
465
|
+
## ๐ Storybook & Next.js
|
|
466
|
+
|
|
467
|
+
- **Storybook decorator**: wrap stories once so hooks have context and tokens.
|
|
257
468
|
|
|
258
469
|
```tsx
|
|
259
|
-
//
|
|
260
|
-
import { FigmaVarsProvider
|
|
470
|
+
// .storybook/preview.tsx
|
|
471
|
+
import { FigmaVarsProvider } from '@figma-vars/hooks'
|
|
472
|
+
import type { Preview } from '@storybook/react'
|
|
473
|
+
|
|
474
|
+
const FIGMA_TOKEN = process.env.STORYBOOK_FIGMA_TOKEN
|
|
475
|
+
const FIGMA_FILE_KEY = process.env.STORYBOOK_FIGMA_FILE_KEY
|
|
476
|
+
|
|
477
|
+
const preview: Preview = {
|
|
478
|
+
decorators: [
|
|
479
|
+
Story => (
|
|
480
|
+
<FigmaVarsProvider
|
|
481
|
+
token={FIGMA_TOKEN}
|
|
482
|
+
fileKey={FIGMA_FILE_KEY}>
|
|
483
|
+
<Story />
|
|
484
|
+
</FigmaVarsProvider>
|
|
485
|
+
),
|
|
486
|
+
],
|
|
487
|
+
}
|
|
261
488
|
|
|
262
|
-
export
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
<TokenList />
|
|
267
|
-
</FigmaVarsProvider>
|
|
268
|
-
)
|
|
489
|
+
export default preview
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
- **Next.js App Router**: provide context in a shared provider file.
|
|
269
493
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
494
|
+
```tsx
|
|
495
|
+
// app/providers.tsx
|
|
496
|
+
import { FigmaVarsProvider } from '@figma-vars/hooks'
|
|
497
|
+
|
|
498
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
499
|
+
return (
|
|
500
|
+
<FigmaVarsProvider
|
|
501
|
+
token={process.env.NEXT_PUBLIC_FIGMA_TOKEN}
|
|
502
|
+
fileKey={process.env.NEXT_PUBLIC_FIGMA_FILE_KEY}>
|
|
503
|
+
{children}
|
|
504
|
+
</FigmaVarsProvider>
|
|
505
|
+
)
|
|
273
506
|
}
|
|
274
507
|
```
|
|
275
508
|
|
|
509
|
+
## ๐งช Tooling & Quality Gates
|
|
510
|
+
|
|
511
|
+
- `pnpm run build`, `pnpm test`, `pnpm run test:coverage`
|
|
512
|
+
- `pnpm run check:publint`, `pnpm run check:attw`, `pnpm run check:size`
|
|
513
|
+
|
|
514
|
+
## ๐งญ Release Checklist (for 3.0.0)
|
|
515
|
+
|
|
516
|
+
- Run `pnpm run check:release`
|
|
517
|
+
- Tag `v3.0.0` (CI publishes to npm)
|
|
518
|
+
- Update dist-tags on npm if needed (`latest` โ 3.0.0)
|
|
519
|
+
|
|
276
520
|
---
|
|
277
521
|
|
|
278
522
|
## ๐ Contributing
|
|
@@ -282,4 +526,4 @@ PRs and issues are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for gu
|
|
|
282
526
|
## ๐ License
|
|
283
527
|
|
|
284
528
|
This project is licensed under the [MIT License](LICENSE).
|
|
285
|
-
ยฉ 2024โ
|
|
529
|
+
ยฉ 2024โ2026 Mark Learst
|