import { Tab, Tabs } from 'nextra-theme-docs';
type IconConfig = {
disabled?: boolean;
nodeIds: string[];
// custom format icon name
iconName: (nameFromFigma: string) => string;
exportPath: string;
// If the field is true, then the SVG sprite will be generated
generateSprite: boolean;
// If the field is true, then the file with icons' names as types will be created
generateTypes: boolean;
// If the field is true, then the extractor will only generate SVG sprite from local icons
// else all available icons will be downloaded and the sprile will be generated from them
localIcons?: boolean;
// It allows changing svgo config if you need or disable at all
// if this field is a function, then the function will be run before svgo optimization. And changed config from this function will be passed to svgo optimization
// if this field is boolean and equals `false` then svgo optimozation won't be run at all
optimizeSvg?: false | (config?: SVGOConfig) => SVGOConfig;
};
type Config = {
// your Figma api access key
apiKey: string;
// Figma file id
fileId: string;
styles: {
// The main path for exporting all requested data
exportPath: string;
/*
Allowed themes if there is at least one styles field with flag `useTheme`
It will parse returned name of colors or etc. and look for allowed themes at the start of names
example: for allowedThemes: ['light','dark'] all returned names will be parsed by looking for 'light' or 'dark' at the start of name
And if color name is 'light/text/text-900' then it will be transformed to 'text/text-900' and relate to the 'light' theme
All others name which are not found by allowed themes will be overlooked.
Note: If there is at least one styles field with flag `useTheme` then this field is required
*/
allowedThemes?: string[];
/**
Default theme for styles fields with flag `useTheme`
It's used like default variables inside generated CSS variables
Note: If there is at least one styles field with flag `useTheme` then this field is required
*/
defaultTheme?: string;
colors?: {
disabled?: boolean;
// custom key name
keyName?: (name?: string, useTheme?: boolean) => string;
// Enable using of theme
useTheme?: boolean;
};
gradients?: {
disabled?: boolean;
// custom key name
keyName?: (name?: string) => string;
};
effects?: {
disabled?: boolean;
// custom key name
keyName?: (name?: string, useTheme?: boolean) => string;
// Enable using of theme
useTheme?: boolean;
};
textStyles?: {
disabled?: boolean;
// custom key name
keyName?: (nameFromFigma: string) => string;
// It allows merging text styles for different screen sizes to a single style
merge?: boolean;
};
};
// Configuration of icons can have more one setting
icons: IconConfig | IconConfig[];
/*
It's used when textStyles -> merge == true,
It contains title for each allowed screen size for generation accurate media queries
*/
screens?: {
[title: string]: number;
};
};
<Tabs items={['Config with disabled theme', 'Config with enabled theme','Config with multi icons']}>
// @ts-check
const { screens } = require('./theme/screens');
function getKeyName(name) {
if (name.toLowerCase().startsWith('ui-kit') || name.toLowerCase().startsWith('ui kit')) {
return 'INTERNAL_DO_NOT_USE';
}
/**
* format name from like:
* "heading/h800 - md" -> "h800-md"
* "heading / h800 - md" -> "h800-md"
* "conventions are ignored/heading/h800 - md bla bla" -> "h800-md"
*/
const resultName = name
.split('/')
.at(-1)?
.replace(' - ', '-')
.split(' ')
.find(name => name !== '');
if (!resultName) {
throw `getKeyName for "${name}" returns an empty string, check getKeyName implementation`;
}
return resultName;
}
const iconNaming = originalName => {
const formattedName = originalName.replace(/ /g, '').replace('/', '-');
return formattedName.toLowerCase();
};
/**
* @type {import('@shakuroinc/figma-extractor').Config}
**/
module.exports = {
apiKey: 'xxxxxx', // your Figma api access key
fileId: 'xxxxxx', // Figma file id
styles: {
exportPath: './theme',
colors: {
keyName: getKeyName, // custom key name
},
effects: {
// keyName: nameFromFigma => nameFromFigma, // custom key name
},
gradients: {
// keyName: nameFromFigma => nameFromFigma, // custom key name
},
textStyles: {
keyName: nameFromFigma => `.v-${getKeyName(nameFromFigma)}`,
merge: true, // or false - it allows merging text styles for different screen sizes to a single style
},
},
icons: {
// disabled: true,
nodeIds: ['2310:0', '2090:11', '276:18'],
iconName: name => iconNaming(name), // custom format icon name
exportPath: './atoms/icon',
generateSprite: true,
generateTypes: true,
localIcons: false,
},
screens: {
bs: 0,
...screens,
},
};
const screens = {
sm: '600px',
md: '979px',
lg: '1200px',
xl: '1600px',
};
module.exports = { screens };
function getKeyName(name) { if (name.toLowerCase().startsWith('ui-kit') || name.toLowerCase().startsWith('ui kit')) { return 'INTERNAL_DO_NOT_USE'; }
/**
- format name from like:
- "heading/h800 - md" -> "h800-md"
- "heading / h800 - md" -> "h800-md"
- "conventions are ignored/heading/h800 - md bla bla" -> "h800-md" */ const resultName = name .split('/') .at(-1)? .replace(' - ', '-') .split(' ') .find(name => name !== '');
if (!resultName) {
throw getKeyName for "${name}" returns an empty string, check getKeyName implementation
;
}
return resultName; }
function getKeyNameWithTheme(name = '') { if (name.toLowerCase().startsWith('ui-kit') || name.toLowerCase().startsWith('ui kit')) { return 'INTERNAL_DO_NOT_USE'; }
const splittedName = name.split('/');
/**
- format name from like:
- "dark/heading/h800 - md" -> "h800-md"
- "dark / heading / h800 - md" -> "h800-md" */ let resultName = splittedName .at(-1) ?.replace(' - ', '-') .split(' ') .find(name => name !== '');
resultName = ${splittedName[0]}/${resultName}
;
if (!resultName) {
throw getKeyNameWithTheme for "${name}" returns an empty string, check getKeyNameWithTheme implementation
;
}
return resultName; }
const iconNaming = originalName => { const formattedName = originalName.replace(/ /g, '').replace('/', '-'); return formattedName.toLowerCase(); };
/**
- @type {import('@shakuroinc/figma-extractor').Config}
**/
module.exports = {
apiKey: 'xxxxxx', // your Figma api access key
fileId: 'xxxxxx', // Figma file id
styles: {
exportPath: './theme',
allowedThemes: ['light','dark'], // allowed themes
defaultTheme: 'light', // one of the allowed themes which will be meant as default theme
colors: {
keyName: getKeyNameWithTheme,
useTheme: true,
},
effects: {
keyName: getKeyNameWithTheme,
useTheme: true,
},
gradients: {
// keyName: nameFromFigma => nameFromFigma
, // custom key name }, textStyles: { keyName: nameFromFigma =>
.v-${getKeyName(nameFromFigma)}`, merge: true, // or false - it allows merging text styles for different screen sizes to a single style }, }, icons: { // disabled: true, nodeIds: ['2310:0', '2090:11', '276:18'], iconName: name => iconNaming(name), // custom format icon name exportPath: './atoms/icon', generateSprite: true, generateTypes: true, localIcons: false, }, screens: { bs: 0, ...screens, }, };
```js filename="/theme/screens.js" copy
const screens = {
sm: '600px',
md: '979px',
lg: '1200px',
xl: '1600px',
};
module.exports = { screens };
In this case, you can use such config:
// @ts-check
const { screens } = require('./theme/screens');
function getKeyName(name) {
if (name.toLowerCase().startsWith('ui-kit') || name.toLowerCase().startsWith('ui kit')) {
return 'INTERNAL_DO_NOT_USE';
}
/**
* format name from like:
* "heading/h800 - md" -> "h800-md"
* "heading / h800 - md" -> "h800-md"
* "conventions are ignored/heading/h800 - md bla bla" -> "h800-md"
*/
const resultName = name
.split('/')
.at(-1)?
.replace(' - ', '-')
.split(' ')
.find(name => name !== '');
if (!resultName) {
throw `getKeyName for "${name}" returns an empty string, check getKeyName implementation`;
}
return resultName;
}
function getKeyNameWithTheme(name = '') {
if (name.toLowerCase().startsWith('ui-kit') || name.toLowerCase().startsWith('ui kit')) {
return 'INTERNAL_DO_NOT_USE';
}
const splittedName = name.split('/');
/**
* format name from like:
* "dark/heading/h800 - md" -> "h800-md"
* "dark / heading / h800 - md" -> "h800-md"
*/
let resultName = splittedName
.at(-1)
?.replace(' - ', '-')
.split(' ')
.find(name => name !== '');
resultName = `${splittedName[0]}/${resultName}`;
if (!resultName) {
throw `getKeyNameWithTheme for "${name}" returns an empty string, check getKeyNameWithTheme implementation`;
}
return resultName;
}
const iconNaming = originalName => {
const formattedName = originalName.replace(/ /g, '').replace('/', '-');
return formattedName.toLowerCase();
};
/**
* @type {import('@shakuroinc/figma-extractor').Config}
**/
module.exports = {
apiKey: 'xxxxxx', // your Figma api access key
fileId: 'xxxxxx', // Figma file id
styles: {
colors: {
keyName: getKeyNameWithTheme,
},
effects: {
keyName: getKeyNameWithTheme,
},
gradients: {
// keyName: nameFromFigma => nameFromFigma`, // custom key name
},
textStyles: {
keyName: nameFromFigma => `.v-${getKeyName(nameFromFigma)}`,
merge: true, // or false - it allows merging text styles for different screen sizes to a single style
},
},
icons: [
// Let's assume this block is used to download black&white icons
{
nodeIds: ['2310:0', '2090:11'],
iconName: name => iconNaming(name), // custom format icon name
exportPath: './atoms/icon',
generateSprite: false,
generateTypes: false,
localIcons: false,
},
// This block is used to download color icons
{
nodeIds: ['276:18', '276:21'],
iconName: name => iconNaming(name), // custom format icon name
exportPath: './atoms/icon',
optimizeSvg: false, // For color icons, we must disable svg optimization otherwise downloaded icons will be changed
generateSprite: false,
generateTypes: false,
localIcons: false,
},
// And the last block is used to generate a sprite and types
{
nodeIds: [],
exportPath: './atoms/icon',
generateSprite: true,
generateTypes: true,
localIcons: true, // This field is required for this block. It allows working only local icons
},
],
screens: {
bs: 0,
...screens,
},
};
const screens = {
sm: '600px',
md: '979px',
lg: '1200px',
xl: '1600px',
};
module.exports = { screens };
The ability to merge text styles by the name's suffix.
Each suffix is one of the defined screen sizes.
for config:
module.exports = {
...
styles: {
exportPath: './theme',
textStyles: {
...,
merge: true,
},
...
},
screen: {
bs: 0, // This is the base screen (default)
sm: 600,
md: 900,
lg: 1200,
},
...
};
Styles like:
- heading/h500 - bs => {fontSize: 12px}
- heading/h500 - sm => {fontSize: 14px}
- heading/h500 - md => {fontSize: 16px}
- heading/h500 - lg => {fontSize: 20px}
will be transformed:
"heading/h500": {
fontSize: '12px',
'@media (min-width: 600): {
fontSize: '14px',
},
'@media (min-width: 900): {
fontSize: '16px',
},
'@media (min-width: 1200): {
fontSize: '20px',
},
}
If the colors
or effects
field has useTheme: true
then CLI will look for allowed themes and creates colors or effects according to such themes.
allowedThemes
- list of the allowed themes. By only these themes, it will be generated particular files.defaultTheme
- one of the allowed themes. Default variables for CSS variables will be taken from this theme.
for config:
module.exports = {
...
allowedThemes: ['light','dark','blue'],
defaultTheme: 'blue',
styles: {
exportPath: './theme',
colors: {
...,
useTheme: true,
},
...
},
...
};
The CLI will generate such files:
export const DEFAULT_THEME = 'blue';
export const THEMES = ['light', 'dark', 'blue'];
export type Theme = typeof THEMES[number];
module.exports = {
"text-txt600":"#000000",
"text-txt700":"#000000",
"text-txt800":"#000000",
"text-txt900":"#000000",
};
module.exports = {
"text-txt600":"#ffffff",
"text-txt700":"#ffffff",
"text-txt800":"#ffffff",
"text-txt900":"#ffffff",
};
module.exports = {
"text-txt600":"#0000ff",
};
module.exports = {
"text-txt600":"#0000ff", // it will be taken from a default theme if it is defined for a specific color
"text-txt700":"",
"text-txt800":"",
"text-txt900":"",
};
module.exports = {
"text-txt600":"var(--sh-text-txt600,'#0000ff')", // it will be taken from a default theme if it is defined for a specific color
"text-txt700":"var(--sh-text-txt700,'')",
"text-txt800":"var(--sh-text-txt800,'')",
"text-txt900":"var(--sh-text-txt900,'')",
};
[data-theme='dark'] {
--sh-text-txt600: #000000;
--sh-text-txt700: #000000;
--sh-text-txt800: #000000;
--sh-text-txt900: #000000;
}
[data-theme='light'] {
--sh-text-txt600: #ffffff;
--sh-text-txt700: #ffffff;
--sh-text-txt800: #ffffff;
--sh-text-txt900: #ffffff;
}
[data-theme='blue'] {
--sh-text-txt600: #0000ff;
}
/* from blue theme due to it is a default theme */
:root {
--sh-text-txt600: #ff00ff;
--sh-text-txt700: '';
--sh-text-txt800: '';
--sh-text-txt900: '';
}
Also, the same files will be generated for useTheme
inside effects section.