diff --git a/packages/tokens-studio-for-figma/src/app/components/ImportVariablesDialog.tsx b/packages/tokens-studio-for-figma/src/app/components/ImportVariablesDialog.tsx new file mode 100644 index 000000000..e578a5072 --- /dev/null +++ b/packages/tokens-studio-for-figma/src/app/components/ImportVariablesDialog.tsx @@ -0,0 +1,201 @@ +import React, { useState, useCallback, useEffect } from 'react'; +import { + Button, Checkbox, Label, Stack, Heading, +} from '@tokens-studio/ui'; +import Modal from './Modal'; +import Box from './Box'; +import { VariableCollectionInfo, SelectedCollections } from '@/types/VariableCollectionSelection'; + +type Props = { + isOpen: boolean; + onClose: () => void; + onConfirm: (selectedCollections: SelectedCollections, options: { useDimensions: boolean; useRem: boolean }) => void; + collections: VariableCollectionInfo[]; +}; + +export default function ImportVariablesDialog({ + isOpen, onClose, onConfirm, collections, +}: Props) { + const [selectedCollections, setSelectedCollections] = useState({}); + const [useDimensions, setUseDimensions] = useState(false); + const [useRem, setUseRem] = useState(false); + + // Initialize all collections as selected with all modes selected by default + useEffect(() => { + if (collections.length > 0) { + const initialSelection: SelectedCollections = {}; + collections.forEach((collection) => { + initialSelection[collection.id] = { + name: collection.name, + selectedModes: collection.modes.map((mode) => mode.modeId), + }; + }); + setSelectedCollections(initialSelection); + } + }, [collections]); + + const handleCollectionToggle = useCallback((collectionId: string, collectionName: string, modes: { modeId: string; name: string }[]) => { + setSelectedCollections((prev) => { + const isCurrentlySelected = prev[collectionId]; + if (isCurrentlySelected) { + // Remove collection + const newSelection = { ...prev }; + delete newSelection[collectionId]; + return newSelection; + } + // Add collection with all modes selected + return { + ...prev, + [collectionId]: { + name: collectionName, + selectedModes: modes.map((mode) => mode.modeId), + }, + }; + }); + }, []); + + const handleModeToggle = useCallback((collectionId: string, modeId: string) => { + setSelectedCollections((prev) => { + const collection = prev[collectionId]; + if (!collection) return prev; + + const isCurrentlySelected = collection.selectedModes.includes(modeId); + const newSelectedModes = isCurrentlySelected + ? collection.selectedModes.filter((id) => id !== modeId) + : [...collection.selectedModes, modeId]; + + // If no modes are selected, remove the collection entirely + if (newSelectedModes.length === 0) { + const newSelection = { ...prev }; + delete newSelection[collectionId]; + return newSelection; + } + + return { + ...prev, + [collectionId]: { + ...collection, + selectedModes: newSelectedModes, + }, + }; + }); + }, []); + + const handleConfirm = useCallback(() => { + onConfirm(selectedCollections, { useDimensions, useRem }); + }, [selectedCollections, useDimensions, useRem, onConfirm]); + + const hasSelections = Object.keys(selectedCollections).length > 0; + + return ( + + + + + )} + > + + + Select which variable collections and modes to import. Sets will be created for each selected mode. + + + {/* Options */} + + Options + + setUseDimensions(checked === true)} + id="useDimensions" + /> + + + + setUseRem(checked === true)} + id="useRem" + /> + + + + + {/* Collections */} + + Variable Collections + {collections.length === 0 ? ( + + There are no collections present in this file + + ) : ( + collections.map((collection) => { + const isCollectionSelected = !!selectedCollections[collection.id]; + const selectedModes = selectedCollections[collection.id]?.selectedModes || []; + const allModesSelected = isCollectionSelected && selectedModes.length === collection.modes.length; + + return ( + + + + handleCollectionToggle(collection.id, collection.name, collection.modes)} + id={`collection-${collection.id}`} + /> + + + + {isCollectionSelected && ( + + {collection.modes.map((mode) => ( + + handleModeToggle(collection.id, mode.modeId)} + id={`mode-${collection.id}-${mode.modeId}`} + /> + + + ))} + + )} + + + ); + }) + )} + + + {!hasSelections && ( + + Please select at least one collection and mode to import. + + )} + + + ); +} diff --git a/packages/tokens-studio-for-figma/src/app/components/StylesDropdown.tsx b/packages/tokens-studio-for-figma/src/app/components/StylesDropdown.tsx index 4309f10c0..2fff1afb1 100644 --- a/packages/tokens-studio-for-figma/src/app/components/StylesDropdown.tsx +++ b/packages/tokens-studio-for-figma/src/app/components/StylesDropdown.tsx @@ -4,23 +4,44 @@ import { useTranslation } from 'react-i18next'; import { DropdownMenu, Button } from '@tokens-studio/ui'; import useTokens from '../store/useTokens'; -import { activeTokenSetReadOnlySelector, editProhibitedSelector } from '@/selectors'; +import { activeTokenSetReadOnlySelector, editProhibitedSelector, themesListSelector } from '@/selectors'; import ManageStylesAndVariables from './ManageStylesAndVariables/ManageStylesAndVariables'; +import ImportVariablesDialog from './ImportVariablesDialog'; +import { useImportVariables } from '../hooks/useImportVariables'; +import { useIsProUser } from '../hooks/useIsProUser'; export default function StylesDropdown() { const editProhibited = useSelector(editProhibitedSelector); const activeTokenSetReadOnly = useSelector(activeTokenSetReadOnlySelector); + const themes = useSelector(themesListSelector); + const proUser = useIsProUser(); const importDisabled = editProhibited || activeTokenSetReadOnly; - const { pullStyles, pullVariables } = useTokens(); + const { pullStyles } = useTokens(); const { t } = useTranslation(['tokens']); const [showModal, setShowModal] = React.useState(false); + const { + isDialogOpen, + collections, + isLoading, + openDialog, + closeDialog, + importVariables, + } = useImportVariables(); const handleOpenModal = useCallback(() => { setShowModal(true); }, [setShowModal]); + const handleImportVariables = useCallback(() => { + openDialog(); + }, [openDialog]); + + const handleConfirmImport = useCallback((selectedCollections, options) => { + importVariables(selectedCollections, options, themes, proUser); + }, [importVariables, themes, proUser]); + return ( <> @@ -33,12 +54,18 @@ export default function StylesDropdown() { {t('exportStylesAndVariables')} - {t('importVariables')} + {t('importVariables')} {t('importStyles')} {showModal && } + ); } diff --git a/packages/tokens-studio-for-figma/src/app/hooks/useImportVariables.tsx b/packages/tokens-studio-for-figma/src/app/hooks/useImportVariables.tsx new file mode 100644 index 000000000..a79dc2b6b --- /dev/null +++ b/packages/tokens-studio-for-figma/src/app/hooks/useImportVariables.tsx @@ -0,0 +1,82 @@ +import { useState, useCallback } from 'react'; +import { AsyncMessageChannel } from '@/AsyncMessageChannel'; +import { AsyncMessageTypes } from '@/types/AsyncMessages'; +import { VariableCollectionInfo, SelectedCollections } from '@/types/VariableCollectionSelection'; +import { ThemeObjectsList } from '@/types/ThemeObjectsList'; +import { track } from '@/utils/analytics'; + +export function useImportVariables() { + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [collections, setCollections] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + const openDialog = useCallback(async () => { + setIsLoading(true); + try { + // Fetch available collections first + const response = await AsyncMessageChannel.ReactInstance.message({ + type: AsyncMessageTypes.GET_AVAILABLE_VARIABLE_COLLECTIONS, + }); + + setCollections(response.collections); + setIsDialogOpen(true); + } catch (error) { + console.error('Error fetching variable collections:', error); + setCollections([]); + } finally { + setIsLoading(false); + } + }, []); + + const closeDialog = useCallback(() => { + setIsDialogOpen(false); + setCollections([]); + }, []); + + const importVariables = useCallback(async ( + selectedCollections: SelectedCollections, + options: { useDimensions: boolean; useRem: boolean }, + themes: ThemeObjectsList, + proUser: boolean, + ) => { + try { + // Calculate analytics data + const selectedCollectionIds = Object.keys(selectedCollections); + const totalSelectedModes = selectedCollectionIds.reduce((count, collectionId) => count + selectedCollections[collectionId].selectedModes.length, 0); + const totalAvailableModes = collections.reduce((count, collection) => count + collection.modes.length, 0); + + // Track analytics + track('Import Variables', { + useDimensions: options.useDimensions, + useRem: options.useRem, + selectedCollections: selectedCollectionIds.length, + totalCollections: collections.length, + selectedModes: totalSelectedModes, + totalModes: totalAvailableModes, + }); + + await AsyncMessageChannel.ReactInstance.message({ + type: AsyncMessageTypes.PULL_VARIABLES, + options: { + useDimensions: options.useDimensions, + useRem: options.useRem, + selectedCollections, + }, + themes, + proUser, + }); + closeDialog(); + } catch (error) { + console.error('Error importing variables:', error); + } + }, [closeDialog, collections]); + + return { + isDialogOpen, + collections, + isLoading, + openDialog, + closeDialog, + importVariables, + }; +} diff --git a/packages/tokens-studio-for-figma/src/app/store/useTokens.test.tsx b/packages/tokens-studio-for-figma/src/app/store/useTokens.test.tsx index 8ca4b815c..6f71dd071 100644 --- a/packages/tokens-studio-for-figma/src/app/store/useTokens.test.tsx +++ b/packages/tokens-studio-for-figma/src/app/store/useTokens.test.tsx @@ -304,14 +304,6 @@ describe('useToken test', () => { await expect(result.current.pullStyles()).resolves.not.toThrow(); }); - it('pullVariables test', async () => { - mockConfirm.mockImplementation(() => Promise.resolve()); - await act(async () => { - await result.current.pullVariables(); - }); - await expect(result.current.pullVariables()).resolves.not.toThrow(); - }); - it('should send message to pull styles from figma', async () => { const messageSpy = jest.spyOn(AsyncMessageChannel.ReactInstance, 'message'); mockConfirm.mockImplementation(() => Promise.resolve({ diff --git a/packages/tokens-studio-for-figma/src/app/store/useTokens.tsx b/packages/tokens-studio-for-figma/src/app/store/useTokens.tsx index d60092d01..e83102bc3 100644 --- a/packages/tokens-studio-for-figma/src/app/store/useTokens.tsx +++ b/packages/tokens-studio-for-figma/src/app/store/useTokens.tsx @@ -189,30 +189,6 @@ export default function useTokens() { } }, [confirm]); - const pullVariables = useCallback(async () => { - const userDecision = await confirm({ - text: 'Import variables', - description: 'Sets will be created for each variable mode.', - choices: [ - { key: 'useDimensions', label: 'Convert numbers to dimensions', enabled: false }, - { key: 'useRem', label: 'Use rem for dimension values', enabled: false }, - ], - confirmAction: 'Import', - }); - - if (userDecision) { - AsyncMessageChannel.ReactInstance.message({ - type: AsyncMessageTypes.PULL_VARIABLES, - options: { - useDimensions: userDecision.data.includes('useDimensions'), - useRem: userDecision.data.includes('useRem'), - }, - themes, - proUser, - }); - } - }, [confirm, themes, proUser]); - const removeTokensByValue = useCallback((data: RemoveTokensByValueData) => { track('removeTokensByValue', { count: data.length }); @@ -779,7 +755,6 @@ export default function useTokens() { createStylesFromSelectedTokenSets, createStylesFromSelectedThemes, pullStyles, - pullVariables, remapToken, remapTokensInGroup, removeTokensByValue, @@ -805,7 +780,6 @@ export default function useTokens() { createStylesFromSelectedTokenSets, createStylesFromSelectedThemes, pullStyles, - pullVariables, remapToken, remapTokensInGroup, removeTokensByValue, diff --git a/packages/tokens-studio-for-figma/src/plugin/asyncMessageHandlers/__tests__/getAvailableVariableCollections.test.ts b/packages/tokens-studio-for-figma/src/plugin/asyncMessageHandlers/__tests__/getAvailableVariableCollections.test.ts new file mode 100644 index 000000000..74f2ed3e3 --- /dev/null +++ b/packages/tokens-studio-for-figma/src/plugin/asyncMessageHandlers/__tests__/getAvailableVariableCollections.test.ts @@ -0,0 +1,54 @@ +import { getAvailableVariableCollections } from '../getAvailableVariableCollections'; + +describe('getAvailableVariableCollections', () => { + it('should return available variable collections', async () => { + const mockCollections = [ + { + id: 'collection1', + name: 'Collection 1', + modes: [ + { modeId: 'mode1', name: 'Light' }, + { modeId: 'mode2', name: 'Dark' }, + ], + }, + { + id: 'collection2', + name: 'Collection 2', + modes: [ + { modeId: 'mode3', name: 'Default' }, + ], + }, + ]; + + global.figma = { + variables: { + getLocalVariableCollectionsAsync: jest.fn().mockResolvedValue(mockCollections), + }, + } as any; + + const result = await getAvailableVariableCollections(); + + expect(result).toEqual({ + collections: mockCollections, + }); + }); + + it('should return empty array if error occurs', async () => { + global.figma = { + variables: { + getLocalVariableCollectionsAsync: jest.fn().mockRejectedValue(new Error('Failed')), + }, + } as any; + + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); + + const result = await getAvailableVariableCollections(); + + expect(result).toEqual({ + collections: [], + }); + expect(consoleSpy).toHaveBeenCalledWith('Error getting variable collections:', expect.any(Error)); + + consoleSpy.mockRestore(); + }); +}); diff --git a/packages/tokens-studio-for-figma/src/plugin/asyncMessageHandlers/getAvailableVariableCollections.ts b/packages/tokens-studio-for-figma/src/plugin/asyncMessageHandlers/getAvailableVariableCollections.ts new file mode 100644 index 000000000..c1b4ffc82 --- /dev/null +++ b/packages/tokens-studio-for-figma/src/plugin/asyncMessageHandlers/getAvailableVariableCollections.ts @@ -0,0 +1,32 @@ +import { AsyncMessageChannelHandlers } from '@/AsyncMessageChannel'; +import { AsyncMessageTypes } from '@/types/AsyncMessages'; +import type { VariableCollectionInfo } from '@/types/VariableCollectionSelection'; + +export const getAvailableVariableCollections: AsyncMessageChannelHandlers[AsyncMessageTypes.GET_AVAILABLE_VARIABLE_COLLECTIONS] = async (): Promise<{ + collections: VariableCollectionInfo[] +}> => { + try { + const allCollections = await figma.variables.getLocalVariableCollectionsAsync(); + console.log('All collections from Figma API:', JSON.stringify(allCollections, null, 2)); + + const collections: VariableCollectionInfo[] = allCollections.map((collection) => { + console.log(`Processing collection: ${collection.id}, name: "${collection.name}"`); + console.log(`Modes for collection ${collection.name}:`, JSON.stringify(collection.modes, null, 2)); + + return { + id: collection.id, + name: collection.name || `Collection ${collection.id.slice(0, 8)}`, + modes: collection.modes.map((mode) => ({ + modeId: mode.modeId, + name: mode.name || `Mode ${mode.modeId.slice(0, 8)}`, + })), + }; + }); + + console.log('Returning collections:', JSON.stringify(collections, null, 2)); + return { collections }; + } catch (error) { + console.error('Error getting variable collections:', error); + return { collections: [] }; + } +}; diff --git a/packages/tokens-studio-for-figma/src/plugin/asyncMessageHandlers/index.ts b/packages/tokens-studio-for-figma/src/plugin/asyncMessageHandlers/index.ts index 1b2a858c5..81a421160 100644 --- a/packages/tokens-studio-for-figma/src/plugin/asyncMessageHandlers/index.ts +++ b/packages/tokens-studio-for-figma/src/plugin/asyncMessageHandlers/index.ts @@ -31,6 +31,7 @@ export * from './removeStyles'; export * from './setAuthData'; export * from './setNoneValuesOnNode'; export * from './getFigmaFonts'; +export * from './getAvailableVariableCollections'; export * from './setUsedEmail'; export * from './createLocalVariables'; export * from './createLocalVariablesWithoutModes'; diff --git a/packages/tokens-studio-for-figma/src/plugin/controller.ts b/packages/tokens-studio-for-figma/src/plugin/controller.ts index 76a811838..f611c2b24 100644 --- a/packages/tokens-studio-for-figma/src/plugin/controller.ts +++ b/packages/tokens-studio-for-figma/src/plugin/controller.ts @@ -60,6 +60,7 @@ AsyncMessageChannel.PluginInstance.handle( AsyncMessageChannel.PluginInstance.handle(AsyncMessageTypes.RESOLVE_STYLE_INFO, asyncHandlers.resolveStyleInfo); AsyncMessageChannel.PluginInstance.handle(AsyncMessageTypes.SET_NONE_VALUES_ON_NODE, asyncHandlers.setNoneValuesOnNode); AsyncMessageChannel.PluginInstance.handle(AsyncMessageTypes.GET_FIGMA_FONTS, asyncHandlers.getFigmaFonts); +AsyncMessageChannel.PluginInstance.handle(AsyncMessageTypes.GET_AVAILABLE_VARIABLE_COLLECTIONS, asyncHandlers.getAvailableVariableCollections); AsyncMessageChannel.PluginInstance.handle(AsyncMessageTypes.REMOVE_STYLES_WITHOUT_CONNECTION, asyncHandlers.removeStylesWithoutConnection); AsyncMessageChannel.PluginInstance.handle(AsyncMessageTypes.SET_AUTH_DATA, asyncHandlers.setAuthData); AsyncMessageChannel.PluginInstance.handle(AsyncMessageTypes.CREATE_LOCAL_VARIABLES, asyncHandlers.createLocalVariables); diff --git a/packages/tokens-studio-for-figma/src/plugin/pullVariables.test.ts b/packages/tokens-studio-for-figma/src/plugin/pullVariables.test.ts index 33735cb20..d6b2de344 100644 --- a/packages/tokens-studio-for-figma/src/plugin/pullVariables.test.ts +++ b/packages/tokens-studio-for-figma/src/plugin/pullVariables.test.ts @@ -776,4 +776,74 @@ describe('pullStyles', () => { ]), ); }); + + it('filters variables by selected collections and modes', async () => { + const selectedCollections = { + 'VariableID:1:0': { + name: 'Collection 1', + selectedModes: ['1:0', '1:1'], // Only Default and Dark modes + }, + }; + + await pullVariables({ + useDimensions: false, + useRem: false, + selectedCollections, + }, [], false); + + const expectedCall = expect.objectContaining({ + colors: expect.arrayContaining([ + expect.objectContaining({ + name: 'Color', + parent: 'Collection 1/Default', + value: '#ffffff', + }), + expect.objectContaining({ + name: 'Color', + parent: 'Collection 1/Dark', + value: '#000000', + }), + ]), + numbers: expect.arrayContaining([ + expect.objectContaining({ + name: 'Number1', + parent: 'Collection 1/Default', + value: 24, + }), + expect.objectContaining({ + name: 'Number1', + parent: 'Collection 1/Dark', + value: 24, + }), + ]), + }); + + expect(notifyStyleValuesSpy).toHaveBeenCalledWith(expectedCall, []); + + // Should NOT include Light or Custom modes + const actualCall = notifyStyleValuesSpy.mock.calls[0][0]; + const lightModeColors = actualCall.colors?.filter((c) => c.parent?.includes('/Light')); + const customModeColors = actualCall.colors?.filter((c) => c.parent?.includes('/Custom')); + + expect(lightModeColors).toHaveLength(0); + expect(customModeColors).toHaveLength(0); + }); + + it('excludes collections not in selectedCollections', async () => { + const selectedCollections = { + // Only including collection that doesn't exist in mock data + nonExistentCollection: { + name: 'Non-existent Collection', + selectedModes: ['mode1'], + }, + }; + + await pullVariables({ + useDimensions: false, + useRem: false, + selectedCollections, + }, [], false); + + expect(notifyStyleValuesSpy).toHaveBeenCalledWith({}, []); + }); }); diff --git a/packages/tokens-studio-for-figma/src/plugin/pullVariables.ts b/packages/tokens-studio-for-figma/src/plugin/pullVariables.ts index f2baf8d33..bfb705c24 100644 --- a/packages/tokens-studio-for-figma/src/plugin/pullVariables.ts +++ b/packages/tokens-studio-for-figma/src/plugin/pullVariables.ts @@ -56,6 +56,15 @@ export default async function pullVariables(options: PullVariablesOptions, theme collectionsCache.set(variable.variableCollectionId, collection); } } + + // Filter collections and modes based on selectedCollections option + if (options.selectedCollections && collection) { + const selectedCollection = options.selectedCollections[collection.id]; + if (!selectedCollection) { + continue; // Skip this collection if it's not selected + } + } + if (collection) { collections.set(collection.name, collection); } @@ -65,6 +74,14 @@ export default async function pullVariables(options: PullVariablesOptions, theme switch (variable.resolvedType) { case 'COLOR': Object.entries(variable.valuesByMode).forEach(([mode, value]) => { + // Filter modes based on selectedCollections option + if (options.selectedCollections && collection) { + const selectedCollection = options.selectedCollections[collection.id]; + if (selectedCollection && !selectedCollection.selectedModes.includes(mode)) { + return; // Skip this mode if it's not selected + } + } + let tokenValue; if (typeof value === 'object' && 'type' in value && value.type === 'VARIABLE_ALIAS') { @@ -88,6 +105,14 @@ export default async function pullVariables(options: PullVariablesOptions, theme break; case 'BOOLEAN': Object.entries(variable.valuesByMode).forEach(([mode, value]) => { + // Filter modes based on selectedCollections option + if (options.selectedCollections && collection) { + const selectedCollection = options.selectedCollections[collection.id]; + if (selectedCollection && !selectedCollection.selectedModes.includes(mode)) { + return; // Skip this mode if it's not selected + } + } + const modeName = collection?.modes.find((m) => m.modeId === mode)?.name; let tokenValue; if (typeof value === 'object' && 'type' in value && value.type === 'VARIABLE_ALIAS') { @@ -108,6 +133,14 @@ export default async function pullVariables(options: PullVariablesOptions, theme break; case 'STRING': Object.entries(variable.valuesByMode).forEach(([mode, value]) => { + // Filter modes based on selectedCollections option + if (options.selectedCollections && collection) { + const selectedCollection = options.selectedCollections[collection.id]; + if (selectedCollection && !selectedCollection.selectedModes.includes(mode)) { + return; // Skip this mode if it's not selected + } + } + const modeName = collection?.modes.find((m) => m.modeId === mode)?.name; let tokenValue; if (typeof value === 'object' && 'type' in value && value.type === 'VARIABLE_ALIAS') { @@ -128,6 +161,14 @@ export default async function pullVariables(options: PullVariablesOptions, theme break; case 'FLOAT': Object.entries(variable.valuesByMode).forEach(([mode, value]) => { + // Filter modes based on selectedCollections option + if (options.selectedCollections && collection) { + const selectedCollection = options.selectedCollections[collection.id]; + if (selectedCollection && !selectedCollection.selectedModes.includes(mode)) { + return; // Skip this mode if it's not selected + } + } + let tokenValue: string | number = value as number; if (typeof value === 'object' && 'type' in value && value.type === 'VARIABLE_ALIAS') { const alias = figma.variables.getVariableById(value.id); @@ -184,7 +225,23 @@ export default async function pullVariables(options: PullVariablesOptions, theme // Process themes if pro user if (proUser) { await Promise.all(Array.from(collections.values()).map(async (collection) => { + // Filter collections based on selectedCollections option + if (options.selectedCollections) { + const selectedCollection = options.selectedCollections[collection.id]; + if (!selectedCollection) { + return; // Skip this collection if it's not selected + } + } + await Promise.all(collection.modes.map(async (mode) => { + // Filter modes based on selectedCollections option + if (options.selectedCollections) { + const selectedCollection = options.selectedCollections[collection.id]; + if (selectedCollection && !selectedCollection.selectedModes.includes(mode.modeId)) { + return; // Skip this mode if it's not selected + } + } + const collectionVariables = localVariables.filter((v) => v.variableCollectionId === collection.id); const variableReferences = collectionVariables.reduce((acc, variable) => ({ diff --git a/packages/tokens-studio-for-figma/src/types/AsyncMessages.ts b/packages/tokens-studio-for-figma/src/types/AsyncMessages.ts index 2ac2f0fa8..c4a35b39e 100644 --- a/packages/tokens-studio-for-figma/src/types/AsyncMessages.ts +++ b/packages/tokens-studio-for-figma/src/types/AsyncMessages.ts @@ -23,6 +23,7 @@ import { RenameVariableToken } from '@/app/store/models/reducers/tokenState'; import { UpdateTokenVariablePayload } from './payloads/UpdateTokenVariablePayload'; import { TokenFormatOptions } from '@/plugin/TokenFormatStoreClass'; import { ExportTokenSet } from './ExportTokenSet'; +import type { VariableCollectionInfo } from './VariableCollectionSelection'; export enum AsyncMessageTypes { // the below messages are going from UI to plugin @@ -73,6 +74,7 @@ export enum AsyncMessageTypes { UPDATE_VARIABLES = 'async/update-variables', SET_INITIAL_LOAD = 'async/set-initial-load', PREVIEW_REQUEST_STARTUP = 'async/preview-request-startup', + GET_AVAILABLE_VARIABLE_COLLECTIONS = 'async/get-available-variable-collections', } export type AsyncMessage = P & { type: T }; @@ -278,6 +280,12 @@ export type GetFigmaFontsMessage = AsyncMessage }>; + +export type GetAvailableVariableCollectionsMessage = AsyncMessage; +export type GetAvailableVariableCollectionsMessageResult = AsyncMessage; + export type RemoveStylesWithoutConnectionMessage = AsyncMessage; @@ -398,6 +406,7 @@ export type AsyncMessages = | ResolveStyleInfo | SetNoneValuesOnNodeAsyncMessage | GetFigmaFontsMessage + | GetAvailableVariableCollectionsMessage | SetAuthDataMessage | SetUsedEmailMessage | CreateLocalVariablesAsyncMessage @@ -446,6 +455,7 @@ export type AsyncMessageResults = | ResolveStyleInfoResult | SetNoneValuesOnNodeAsyncMessageResult | GetFigmaFontsMessageResult + | GetAvailableVariableCollectionsMessageResult | SetAuthDataMessageResult | SetUsedEmailMessageResult | CreateLocalVariablesAsyncMessageResult diff --git a/packages/tokens-studio-for-figma/src/types/PullVariablesOptions.ts b/packages/tokens-studio-for-figma/src/types/PullVariablesOptions.ts index 98c74caf4..226aaa1fa 100644 --- a/packages/tokens-studio-for-figma/src/types/PullVariablesOptions.ts +++ b/packages/tokens-studio-for-figma/src/types/PullVariablesOptions.ts @@ -1,4 +1,7 @@ +import type { SelectedCollections } from './VariableCollectionSelection'; + export type PullVariablesOptions = { useDimensions?: boolean; useRem?: boolean; + selectedCollections?: SelectedCollections; }; diff --git a/packages/tokens-studio-for-figma/src/types/VariableCollectionSelection.ts b/packages/tokens-studio-for-figma/src/types/VariableCollectionSelection.ts new file mode 100644 index 000000000..8cd97a861 --- /dev/null +++ b/packages/tokens-studio-for-figma/src/types/VariableCollectionSelection.ts @@ -0,0 +1,15 @@ +export type VariableCollectionInfo = { + id: string; + name: string; + modes: { + modeId: string; + name: string; + }[]; +}; + +export type SelectedCollections = { + [collectionId: string]: { + name: string; + selectedModes: string[]; // array of modeIds + }; +}; diff --git a/packages/tokens-studio-for-figma/src/types/index.ts b/packages/tokens-studio-for-figma/src/types/index.ts index da3d38580..ba4c9e95d 100644 --- a/packages/tokens-studio-for-figma/src/types/index.ts +++ b/packages/tokens-studio-for-figma/src/types/index.ts @@ -12,3 +12,4 @@ export * from './ThemeObjectsList'; export * from './DeepTokensMap'; export * from './MapValuesToTokensResult'; export * from './ShowFormOptions'; +export * from './VariableCollectionSelection'; diff --git a/yarn.lock b/yarn.lock index b5e6d6bbf..34b41fc69 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3415,19 +3415,7 @@ dependencies: "@babel/runtime" "^7.13.10" -"@radix-ui/react-dismissable-layer@1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.4.tgz#883a48f5f938fa679427aa17fcba70c5494c6978" - integrity sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg== - dependencies: - "@babel/runtime" "^7.13.10" - "@radix-ui/primitive" "1.0.1" - "@radix-ui/react-compose-refs" "1.0.1" - "@radix-ui/react-primitive" "1.0.3" - "@radix-ui/react-use-callback-ref" "1.0.1" - "@radix-ui/react-use-escape-keydown" "1.0.3" - -"@radix-ui/react-dismissable-layer@1.0.5": +"@radix-ui/react-dismissable-layer@1.0.4", "@radix-ui/react-dismissable-layer@1.0.5": version "1.0.5" resolved "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz" integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g== @@ -18573,7 +18561,7 @@ react-window@^1.8.8: "@babel/runtime" "^7.0.0" memoize-one ">=3.1.1 <6" -react@^18.2.0: +react@^18, react@^18.2.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==