@figma-vars/hooks 2.0.0-beta.1 → 2.0.0-beta.2

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 CHANGED
@@ -26,6 +26,8 @@ Built for the modern web, this library provides a suite of hooks to fetch, manag
26
26
  - **✍️ Ergonomic Mutations**: A `useMutation`-style API for creating, updating, and deleting variables, providing clear loading and error states.
27
27
  - **🔒 TypeScript-first**: Strictly typed for an ergonomic and safe developer experience. Get autocompletion for all API responses.
28
28
  - **📖 Storybook & Next.js Ready**: Perfect for building live design token dashboards or style guides.
29
+ - **🔄 Local JSON Support**: Use local JSON files exported from Figma Dev Mode plugins when you don't have a Figma Enterprise account, enabling offline development and static deployments.
30
+ - **🚧 Style Dictionary Integration**: Coming soon in future beta releases - seamless integration with Amazon's Style Dictionary for multi-platform design token distribution.
29
31
 
30
32
  ---
31
33
 
@@ -162,6 +164,61 @@ function CreateVariableForm({ collectionId }: { collectionId: string }) {
162
164
  }
163
165
  ```
164
166
 
167
+ ### Using Local JSON Files (No Enterprise Account Required)
168
+
169
+ If you don't have a Figma Enterprise account (required for the Variables API), you can still use this library with local JSON files exported from Figma Dev Mode plugins. This is perfect for:
170
+
171
+ - **Offline Development**: Work on your design system without an internet connection
172
+ - **Static Deployments**: Deploy design token dashboards to static hosting
173
+ - **CI/CD Pipelines**: Use exported JSON files in automated workflows
174
+ - **Team Collaboration**: Share design tokens without API access
175
+
176
+ #### Getting Your JSON File
177
+
178
+ We recommend using the [Variables Exporter for Dev Mode](https://www.figma.com/community/plugin/1491572182178544621) plugin:
179
+
180
+ 1. Install the plugin in Figma
181
+ 2. Open your Figma file in Dev Mode
182
+ 3. Run the plugin and export your variables as JSON
183
+ 4. Save the JSON file to your project (e.g., `src/assets/figma-variables.json`)
184
+
185
+ This plugin exports the exact same format that the Figma Variables API returns, ensuring perfect compatibility with this library.
186
+
187
+ #### Using the Fallback File
188
+
189
+ ```tsx
190
+ // src/main.tsx or App.tsx
191
+ import React from 'react'
192
+ import ReactDOM from 'react-dom/client'
193
+ import { FigmaVarsProvider } from '@figma-vars/hooks'
194
+ import App from './App'
195
+ import variablesData from './assets/figma-variables.json'
196
+
197
+ ReactDOM.createRoot(document.getElementById('root')!).render(
198
+ <React.StrictMode>
199
+ <FigmaVarsProvider
200
+ token={null} // No token needed when using fallbackFile
201
+ fileKey="your-figma-file-key"
202
+ fallbackFile={variablesData}>
203
+ <App />
204
+ </FigmaVarsProvider>
205
+ </React.StrictMode>
206
+ )
207
+ ```
208
+
209
+ You can also pass the JSON as a string if you prefer:
210
+
211
+ ```tsx
212
+ import variablesJson from './assets/figma-variables.json?raw'
213
+
214
+ <FigmaVarsProvider
215
+ token={null}
216
+ fileKey="your-figma-file-key"
217
+ fallbackFile={variablesJson}>
218
+ <App />
219
+ </FigmaVarsProvider>
220
+ ```
221
+
165
222
  ### Figma PAT Security
166
223
 
167
224
  When using the Figma API, it's essential to keep your Personal Access Token (PAT) secure. Here are some best practices:
@@ -231,11 +288,19 @@ function ErrorHandling() {
231
288
 
232
289
  ### Core Hooks
233
290
 
234
- - `useVariables()`: Fetches all local variables for the file key provided to the `FigmaVarsProvider`. Returns a SWR hook state with `data`, `isLoading`, and `error` properties. The actual Figma response is in `data.data`.
291
+ - `useVariables()`: Fetches all local variables for the file key provided to the `FigmaVarsProvider`. Returns a SWR hook state with `data`, `isLoading`, and `error` properties. The actual Figma response is in `data.data`. When `fallbackFile` is provided, it uses the local JSON data instead of making an API request.
235
292
  - `useVariableCollections()`: A convenience hook that returns just the variable collections from the main `useVariables` data.
236
293
  - `useVariableModes()`: A convenience hook that returns just the variable modes from the main `useVariables` data.
237
294
  - `useFigmaToken()`: A simple hook to access the token and file key from the context.
238
295
 
296
+ ### Provider Props
297
+
298
+ The `FigmaVarsProvider` accepts the following props:
299
+
300
+ - `token`: Figma Personal Access Token (PAT) for API authentication. Can be `null` when using `fallbackFile`.
301
+ - `fileKey`: Figma file key for the target file. Required for API requests but can be any string when using `fallbackFile`.
302
+ - `fallbackFile`: Optional local JSON file (as object or string) to use instead of API requests. Perfect for users without Figma Enterprise accounts.
303
+
239
304
  ### Mutation Hooks
240
305
 
241
306
  All mutation hooks return an object with the following shape: `{ mutate, data, isLoading, error }`.
@@ -4,7 +4,9 @@ import { LocalVariablesResponse } from '../types/figma';
4
4
  *
5
5
  * @remarks
6
6
  * This hook uses SWR for caching and revalidation. It fetches the variables for the
7
- * file key provided via the FigmaVarsProvider context.
7
+ * file key provided via the FigmaVarsProvider context. If a fallbackFile is provided,
8
+ * it will use that instead of making an API request, which is useful for users without
9
+ * Figma Enterprise accounts or for offline development.
8
10
  *
9
11
  * @returns SWR response object with `data`, `error`, `isLoading`, and `isValidating`.
10
12
  *
@@ -1 +1 @@
1
- {"version":3,"file":"useVariables.d.ts","sourceRoot":"","sources":["../../src/hooks/useVariables.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAG1D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,0LAUxB,CAAC"}
1
+ {"version":3,"file":"useVariables.d.ts","sourceRoot":"","sources":["../../src/hooks/useVariables.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAG1D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,0LAoBxB,CAAC"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("react/jsx-runtime"),i=require("react"),_=require("swr"),p=i.createContext(void 0),v=({children:e,token:o,fileKey:t,fallbackFile:r})=>{const s=r===void 0?{token:o,fileKey:t}:{token:o,fileKey:t,fallbackFile:r};return b.jsx(p.Provider,{value:s,children:e})},l=()=>{const e=i.useContext(p);if(e===void 0)throw new Error("useFigmaTokenContext must be used within a FigmaVarsProvider");return e},h="https://api.figma.com",w=`${h}/v1/variables`,T=w,A=e=>`${w}/${e}`,I="application/json",y="X-FIGMA-TOKEN",c="A Figma API token is required.",g="An error occurred while fetching data from the Figma API.";async function V(e,o){if(!o)throw new Error(c);const t=await fetch(e,{method:"GET",headers:{[y]:o,"Content-Type":I}});if(!t.ok){let r=g;try{const s=await t.json();s!=null&&s.message&&(r=s.message)}catch{}throw new Error(r)}return t.json()}const E=()=>{const{token:e,fileKey:o}=l(),t=e&&o?`https://api.figma.com/v1/files/${o}/variables/local`:null;return _(t&&e?[t,e]:null,t&&e?([s,n])=>V(s,n):()=>Promise.resolve(void 0))},R=()=>{const{data:e}=E(),o=i.useMemo(()=>e!=null&&e.meta?Object.values(e.meta.variableCollections):[],[e]),t=i.useMemo(()=>e!=null&&e.meta?e.meta.variableCollections:{},[e]);return{collections:o,collectionsById:t}},C=()=>{const{data:e}=E();return i.useMemo(()=>{const o=[],t={},r={};if(e!=null&&e.meta)for(const s of Object.values(e.meta.variableCollections)){o.push(...s.modes),t[s.id]=s.modes;for(const n of s.modes)r[n.modeId]=n}return{modes:o,modesByCollectionId:t,modesById:r}},[e])};function M(e,o){switch(o.type){case"loading":return{...e,status:"loading",error:null};case"success":return{...e,status:"success",data:o.payload};case"error":return{...e,status:"error",error:o.payload};default:return e}}const d=e=>{const o={status:"idle",data:null,error:null},[t,r]=i.useReducer(M,o);return{mutate:i.useCallback(async n=>{r({type:"loading"});try{const a=await e(n);return r({type:"success",payload:a}),a}catch(a){r({type:"error",payload:a});return}},[e]),...t,isLoading:t.status==="loading",isSuccess:t.status==="success",isError:t.status==="error"}};async function m(e,o,t,r){if(!o)throw new Error(c);const a={method:{CREATE:"POST",UPDATE:"PUT",DELETE:"DELETE"}[t],headers:{"Content-Type":"application/json",[y]:o}};r&&(a.body=JSON.stringify(r));const u=await fetch(`${h}${e}`,a);if(!u.ok){const f=await u.json().catch(()=>({}));throw new Error(f.err||f.message||"An API error occurred")}return u.status===204||!u.body?{}:u.json()}const P=()=>{const{token:e}=l();return d(async t=>{if(!e)throw new Error(c);return await m(T,e,"CREATE",t)})},O=()=>{const{token:e}=l();return d(async({variableId:t,payload:r})=>{if(!e)throw new Error(c);return await m(A(t),e,"UPDATE",r)})},D=()=>{const{token:e}=l();return d(async t=>{if(!e)throw new Error(c);return await m(A(t),e,"DELETE",void 0)})},F=()=>{const{token:e}=l();return d(async t=>{if(!e)throw new Error(c);return await m(T,e,"CREATE",t)})};function S(e,o){return e.filter(t=>{let r=!0;return o.resolvedType&&(r=r&&t.resolvedType===o.resolvedType),o.name&&(r=r&&t.name.includes(o.name)),r})}exports.FigmaVarsProvider=v;exports.filterVariables=S;exports.useBulkUpdateVariables=F;exports.useCreateVariable=P;exports.useDeleteVariable=D;exports.useUpdateVariable=O;exports.useVariableCollections=R;exports.useVariableModes=C;exports.useVariables=E;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("react/jsx-runtime"),i=require("react"),_=require("swr"),p=i.createContext(void 0),g=({children:e,token:r,fileKey:t,fallbackFile:o})=>{const s=o===void 0?{token:r,fileKey:t}:{token:r,fileKey:t,fallbackFile:o};return b.jsx(p.Provider,{value:s,children:e})},l=()=>{const e=i.useContext(p);if(e===void 0)throw new Error("useFigmaTokenContext must be used within a FigmaVarsProvider");return e},h="https://api.figma.com",w=`${h}/v1/variables`,T=w,y=e=>`${w}/${e}`,v="application/json",A="X-FIGMA-TOKEN",u="A Figma API token is required.",I="An error occurred while fetching data from the Figma API.";async function V(e,r){if(!r)throw new Error(u);const t=await fetch(e,{method:"GET",headers:{[A]:r,"Content-Type":v}});if(!t.ok){let o=I;try{const s=await t.json();s!=null&&s.message&&(o=s.message)}catch{}throw new Error(o)}return t.json()}const E=()=>{const{token:e,fileKey:r,fallbackFile:t}=l(),o=async(n,a)=>t?typeof t=="string"?JSON.parse(t):t:V(n,a),s=e&&r?`https://api.figma.com/v1/files/${r}/variables/local`:null;return _(s&&e?[s,e]:null,s&&e?([n,a])=>o(n,a):()=>Promise.resolve(void 0))},R=()=>{const{data:e}=E(),r=i.useMemo(()=>e!=null&&e.meta?Object.values(e.meta.variableCollections):[],[e]),t=i.useMemo(()=>e!=null&&e.meta?e.meta.variableCollections:{},[e]);return{collections:r,collectionsById:t}},C=()=>{const{data:e}=E();return i.useMemo(()=>{const r=[],t={},o={};if(e!=null&&e.meta)for(const s of Object.values(e.meta.variableCollections)){r.push(...s.modes),t[s.id]=s.modes;for(const c of s.modes)o[c.modeId]=c}return{modes:r,modesByCollectionId:t,modesById:o}},[e])};function M(e,r){switch(r.type){case"loading":return{...e,status:"loading",error:null};case"success":return{...e,status:"success",data:r.payload};case"error":return{...e,status:"error",error:r.payload};default:return e}}const d=e=>{const r={status:"idle",data:null,error:null},[t,o]=i.useReducer(M,r);return{mutate:i.useCallback(async c=>{o({type:"loading"});try{const n=await e(c);return o({type:"success",payload:n}),n}catch(n){o({type:"error",payload:n});return}},[e]),...t,isLoading:t.status==="loading",isSuccess:t.status==="success",isError:t.status==="error"}};async function m(e,r,t,o){if(!r)throw new Error(u);const n={method:{CREATE:"POST",UPDATE:"PUT",DELETE:"DELETE"}[t],headers:{"Content-Type":"application/json",[A]:r}};o&&(n.body=JSON.stringify(o));const a=await fetch(`${h}${e}`,n);if(!a.ok){const f=await a.json().catch(()=>({}));throw new Error(f.err||f.message||"An API error occurred")}return a.status===204||!a.body?{}:a.json()}const P=()=>{const{token:e}=l();return d(async t=>{if(!e)throw new Error(u);return await m(T,e,"CREATE",t)})},F=()=>{const{token:e}=l();return d(async({variableId:t,payload:o})=>{if(!e)throw new Error(u);return await m(y(t),e,"UPDATE",o)})},O=()=>{const{token:e}=l();return d(async t=>{if(!e)throw new Error(u);return await m(y(t),e,"DELETE",void 0)})},S=()=>{const{token:e}=l();return d(async t=>{if(!e)throw new Error(u);return await m(T,e,"CREATE",t)})};function D(e,r){return e.filter(t=>{let o=!0;return r.resolvedType&&(o=o&&t.resolvedType===r.resolvedType),r.name&&(o=o&&t.name.includes(r.name)),o})}exports.FigmaVarsProvider=g;exports.filterVariables=D;exports.useBulkUpdateVariables=S;exports.useCreateVariable=P;exports.useDeleteVariable=O;exports.useUpdateVariable=F;exports.useVariableCollections=R;exports.useVariableModes=C;exports.useVariables=E;
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _ } from "react/jsx-runtime";
2
- import { createContext as I, useContext as v, useMemo as d, useReducer as R, useCallback as g } from "react";
2
+ import { createContext as I, useContext as v, useMemo as d, useReducer as g, useCallback as R } from "react";
3
3
  import C from "swr";
4
- const f = I(void 0), N = ({
4
+ const f = I(void 0), S = ({
5
5
  children: t,
6
6
  token: o,
7
7
  fileKey: e,
@@ -14,10 +14,10 @@ const f = I(void 0), N = ({
14
14
  if (t === void 0)
15
15
  throw new Error("useFigmaTokenContext must be used within a FigmaVarsProvider");
16
16
  return t;
17
- }, p = "https://api.figma.com", h = `${p}/v1/variables`, w = h, A = (t) => `${h}/${t}`, b = "application/json", T = "X-FIGMA-TOKEN", c = "A Figma API token is required.", P = "An error occurred while fetching data from the Figma API.";
18
- async function M(t, o) {
17
+ }, p = "https://api.figma.com", h = `${p}/v1/variables`, w = h, A = (t) => `${h}/${t}`, b = "application/json", T = "X-FIGMA-TOKEN", i = "A Figma API token is required.", P = "An error occurred while fetching data from the Figma API.";
18
+ async function F(t, o) {
19
19
  if (!o)
20
- throw new Error(c);
20
+ throw new Error(i);
21
21
  const e = await fetch(t, {
22
22
  method: "GET",
23
23
  headers: {
@@ -37,12 +37,12 @@ async function M(t, o) {
37
37
  return e.json();
38
38
  }
39
39
  const y = () => {
40
- const { token: t, fileKey: o } = u(), e = t && o ? `https://api.figma.com/v1/files/${o}/variables/local` : null;
40
+ const { token: t, fileKey: o, fallbackFile: e } = u(), r = async (n, a) => e ? typeof e == "string" ? JSON.parse(e) : e : F(n, a), s = t && o ? `https://api.figma.com/v1/files/${o}/variables/local` : null;
41
41
  return C(
42
- e && t ? [e, t] : null,
43
- e && t ? ([s, n]) => M(s, n) : () => Promise.resolve(void 0)
42
+ s && t ? [s, t] : null,
43
+ s && t ? ([n, a]) => r(n, a) : () => Promise.resolve(void 0)
44
44
  );
45
- }, S = () => {
45
+ }, V = () => {
46
46
  const { data: t } = y(), o = d(
47
47
  () => t != null && t.meta ? Object.values(t.meta.variableCollections) : [],
48
48
  [t]
@@ -54,15 +54,15 @@ const y = () => {
54
54
  collections: o,
55
55
  collectionsById: e
56
56
  };
57
- }, B = () => {
57
+ }, k = () => {
58
58
  const { data: t } = y();
59
59
  return d(() => {
60
60
  const o = [], e = {}, r = {};
61
61
  if (t != null && t.meta)
62
62
  for (const s of Object.values(t.meta.variableCollections)) {
63
63
  o.push(...s.modes), e[s.id] = s.modes;
64
- for (const n of s.modes)
65
- r[n.modeId] = n;
64
+ for (const c of s.modes)
65
+ r[c.modeId] = c;
66
66
  }
67
67
  return {
68
68
  modes: o,
@@ -88,16 +88,16 @@ const l = (t) => {
88
88
  status: "idle",
89
89
  data: null,
90
90
  error: null
91
- }, [e, r] = R(O, o);
91
+ }, [e, r] = g(O, o);
92
92
  return {
93
- mutate: g(
94
- async (n) => {
93
+ mutate: R(
94
+ async (c) => {
95
95
  r({ type: "loading" });
96
96
  try {
97
- const a = await t(n);
98
- return r({ type: "success", payload: a }), a;
99
- } catch (a) {
100
- r({ type: "error", payload: a });
97
+ const n = await t(c);
98
+ return r({ type: "success", payload: n }), n;
99
+ } catch (n) {
100
+ r({ type: "error", payload: n });
101
101
  return;
102
102
  }
103
103
  },
@@ -111,8 +111,8 @@ const l = (t) => {
111
111
  };
112
112
  async function m(t, o, e, r) {
113
113
  if (!o)
114
- throw new Error(c);
115
- const a = {
114
+ throw new Error(i);
115
+ const n = {
116
116
  method: {
117
117
  CREATE: "POST",
118
118
  UPDATE: "PUT",
@@ -123,19 +123,19 @@ async function m(t, o, e, r) {
123
123
  [T]: o
124
124
  }
125
125
  };
126
- r && (a.body = JSON.stringify(r));
127
- const i = await fetch(`${p}${t}`, a);
128
- if (!i.ok) {
129
- const E = await i.json().catch(() => ({}));
126
+ r && (n.body = JSON.stringify(r));
127
+ const a = await fetch(`${p}${t}`, n);
128
+ if (!a.ok) {
129
+ const E = await a.json().catch(() => ({}));
130
130
  throw new Error(E.err || E.message || "An API error occurred");
131
131
  }
132
- return i.status === 204 || !i.body ? {} : i.json();
132
+ return a.status === 204 || !a.body ? {} : a.json();
133
133
  }
134
- const G = () => {
134
+ const B = () => {
135
135
  const { token: t } = u();
136
136
  return l(async (e) => {
137
137
  if (!t)
138
- throw new Error(c);
138
+ throw new Error(i);
139
139
  return await m(
140
140
  w,
141
141
  t,
@@ -143,12 +143,12 @@ const G = () => {
143
143
  e
144
144
  );
145
145
  });
146
- }, j = () => {
146
+ }, G = () => {
147
147
  const { token: t } = u();
148
148
  return l(
149
149
  async ({ variableId: e, payload: r }) => {
150
150
  if (!t)
151
- throw new Error(c);
151
+ throw new Error(i);
152
152
  return await m(
153
153
  A(e),
154
154
  t,
@@ -157,11 +157,11 @@ const G = () => {
157
157
  );
158
158
  }
159
159
  );
160
- }, k = () => {
160
+ }, j = () => {
161
161
  const { token: t } = u();
162
162
  return l(async (e) => {
163
163
  if (!t)
164
- throw new Error(c);
164
+ throw new Error(i);
165
165
  return await m(
166
166
  A(e),
167
167
  t,
@@ -173,7 +173,7 @@ const G = () => {
173
173
  const { token: t } = u();
174
174
  return l(async (e) => {
175
175
  if (!t)
176
- throw new Error(c);
176
+ throw new Error(i);
177
177
  return await m(
178
178
  w,
179
179
  t,
@@ -189,13 +189,13 @@ function x(t, o) {
189
189
  });
190
190
  }
191
191
  export {
192
- N as FigmaVarsProvider,
192
+ S as FigmaVarsProvider,
193
193
  x as filterVariables,
194
194
  L as useBulkUpdateVariables,
195
- G as useCreateVariable,
196
- k as useDeleteVariable,
197
- j as useUpdateVariable,
198
- S as useVariableCollections,
199
- B as useVariableModes,
195
+ B as useCreateVariable,
196
+ j as useDeleteVariable,
197
+ G as useUpdateVariable,
198
+ V as useVariableCollections,
199
+ k as useVariableModes,
200
200
  y as useVariables
201
201
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@figma-vars/hooks",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.2",
4
4
  "description": "Typed React hooks for managing Figma Variables, modes, tokens, and bindings via API.",
5
5
  "author": "Mark Learst",
6
6
  "license": "MIT",