diff --git a/.changeset/attach-all-variables-feature.md b/.changeset/attach-all-variables-feature.md new file mode 100644 index 0000000000..9ff72e2f63 --- /dev/null +++ b/.changeset/attach-all-variables-feature.md @@ -0,0 +1,5 @@ +--- +"@tokens-studio/figma-plugin": patch +--- + +Add "Attach all variables" button to Manage themes dialog that attaches local variables to all themes at once, respecting collection and mode names. Also add loading prop to "Attach local variables" and "Attach local styles" buttons to show progress feedback. diff --git a/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/ManageThemesModal.tsx b/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/ManageThemesModal.tsx index c031c0f627..9e2d2e0f35 100644 --- a/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/ManageThemesModal.tsx +++ b/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/ManageThemesModal.tsx @@ -7,9 +7,11 @@ import debounce from 'lodash.debounce'; import { Button, EmptyState } from '@tokens-studio/ui'; import { styled } from '@stitches/react'; import { useTranslation } from 'react-i18next'; -import { activeThemeSelector, themesListSelector } from '@/selectors'; +import { + activeThemeSelector, themesListSelector, tokensSelector, isWaitingForBackgroundJobSelector, +} from '@/selectors'; import Modal from '../Modal'; -import { Dispatch } from '@/app/store'; +import { Dispatch, RootState } from '@/app/store'; import Stack from '../Stack'; import IconPlus from '@/icons/plus.svg'; import { CreateOrEditThemeForm, FormValues } from './CreateOrEditThemeForm'; @@ -26,6 +28,10 @@ import { TreeItem, themeListToTree } from '@/utils/themeListToTree'; import { ItemData } from '@/context'; import { checkReorder } from '@/utils/motion'; import { ensureFolderIsTogether, findOrderableTargetIndexesInThemeList } from '@/utils/dragDropOrder'; +import { AsyncMessageChannel } from '@/AsyncMessageChannel'; +import { AsyncMessageTypes } from '@/types/AsyncMessages'; +import { BackgroundJobs } from '@/constants/BackgroundJobs'; +import { wrapTransaction } from '@/profiling/transaction'; type Props = unknown; @@ -44,6 +50,10 @@ export const ManageThemesModal: React.FC(); const themes = useSelector(themesListSelector); const activeTheme = useSelector(activeThemeSelector); + const tokens = useSelector(tokensSelector); + const isAttachingLocalVariables = useSelector(useCallback((state: RootState) => ( + isWaitingForBackgroundJobSelector(state, BackgroundJobs.UI_ATTACHING_LOCAL_VARIABLES) + ), [])); const { confirm } = useConfirm(); const [themeEditorOpen, setThemeEditorOpen] = useState(false); const [themeListScrollPosition, setThemeListScrollPosition] = useState(0); @@ -186,6 +196,47 @@ export const ManageThemesModal: React.FC debounce(handleThemeListScroll, 200), [handleThemeListScroll]); + const handleAttachAllVariables = useCallback(async () => { + if (themes.length === 0) return; + + dispatch.uiState.startJob({ + name: BackgroundJobs.UI_ATTACHING_LOCAL_VARIABLES, + isInfinite: true, + }); + + const themeVariableResults: Record = {}; + + for (const theme of themes) { + try { + const result = await wrapTransaction({ name: 'attachVariables' }, async () => await AsyncMessageChannel.ReactInstance.message({ + type: AsyncMessageTypes.ATTACH_LOCAL_VARIABLES_TO_THEME, + tokens, + theme, + })); + + if (result.variableInfo) { + themeVariableResults[theme.id] = result.variableInfo; + } + } catch (error) { + console.error(`Error attaching variables to theme ${theme.name}:`, error); + } + } + + const totalAttached = Object.values(themeVariableResults).reduce((sum, info) => ( + sum + Object.values(info.variableIds || {}).length + ), 0); + + if (totalAttached > 0) { + track('Attach variables to all themes', { + count: totalAttached, + themesProcessed: Object.keys(themeVariableResults).length, + }); + dispatch.tokenState.assignVariableIdsToTheme(themeVariableResults); + } + + dispatch.uiState.completeJob(BackgroundJobs.UI_ATTACHING_LOCAL_VARIABLES); + }, [themes, tokens, dispatch]); + return ( {!themeEditorOpen && ( - + <> + + + )} {themeEditorOpen && ( <> diff --git a/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/ThemeStyleManagementCategory.tsx b/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/ThemeStyleManagementCategory.tsx index da375aaa3d..89e71f0f91 100644 --- a/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/ThemeStyleManagementCategory.tsx +++ b/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/ThemeStyleManagementCategory.tsx @@ -73,6 +73,7 @@ export const ThemeStyleManagementCategory: React.FC {t('attachLocalStyles')} diff --git a/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/ThemeVariableManagement.tsx b/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/ThemeVariableManagement.tsx index 6aa2eba5b0..01cbac9dc0 100644 --- a/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/ThemeVariableManagement.tsx +++ b/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/ThemeVariableManagement.tsx @@ -133,6 +133,7 @@ export const ThemeVariableManagement: React.FC Attach local variables diff --git a/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/__tests__/ManageThemesModal.test.tsx b/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/__tests__/ManageThemesModal.test.tsx index 545dbccb46..35d6e82513 100644 --- a/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/__tests__/ManageThemesModal.test.tsx +++ b/packages/tokens-studio-for-figma/src/app/components/ManageThemesModal/__tests__/ManageThemesModal.test.tsx @@ -14,6 +14,14 @@ jest.mock('../../../hooks/useConfirm', () => ({ }), })); +jest.mock('@/AsyncMessageChannel', () => ({ + AsyncMessageChannel: { + ReactInstance: { + message: jest.fn(), + }, + }, +})); + describe('ManageThemesModal', () => { it('should render', () => { const mockStore = createMockStore({ @@ -89,4 +97,46 @@ describe('ManageThemesModal', () => { expect(result.getByText('delete')).toBeInTheDocument(); }); }); + + it('should render attach all variables button', () => { + const mockStore = createMockStore({ + tokenState: { + themes: [ + { + id: 'light', + name: 'Light', + selectedTokenSets: {}, + $figmaStyleReferences: {}, + }, + { + id: 'dark', + name: 'Dark', + selectedTokenSets: {}, + $figmaStyleReferences: {}, + }, + ], + }, + }); + + const result = render( + + + , + ); + + expect(result.getByTestId('button-manage-themes-modal-attach-all-variables')).toBeInTheDocument(); + }); + + it('should disable attach all variables button when no themes', () => { + const mockStore = createMockStore({}); + + const result = render( + + + , + ); + + const button = result.getByTestId('button-manage-themes-modal-attach-all-variables'); + expect(button).toBeDisabled(); + }); }); diff --git a/packages/tokens-studio-for-figma/src/i18n/lang/en/tokens.json b/packages/tokens-studio-for-figma/src/i18n/lang/en/tokens.json index c05167aa36..974298a840 100644 --- a/packages/tokens-studio-for-figma/src/i18n/lang/en/tokens.json +++ b/packages/tokens-studio-for-figma/src/i18n/lang/en/tokens.json @@ -36,6 +36,7 @@ "returnToOverview": "Return to overview", "stylesVarMultiDimensionalThemesWarning": "Note: When using multi-dimensional themes where values depend on tokens of another theme, connecting styles might not work as expected.", "attachLocalStyles": "Attach local styles", + "attachAllVariables": "Attach all variables", "selectAll": "Select all", "detachSelected": "Detach selected", "saveTheme": "Save theme", diff --git a/packages/tokens-studio-for-figma/src/i18n/lang/es/tokens.json b/packages/tokens-studio-for-figma/src/i18n/lang/es/tokens.json index e669308d2a..c4cdfe7120 100644 --- a/packages/tokens-studio-for-figma/src/i18n/lang/es/tokens.json +++ b/packages/tokens-studio-for-figma/src/i18n/lang/es/tokens.json @@ -223,6 +223,7 @@ "returnToOverview": "Volver a la descripción general", "stylesVarMultiDimensionalThemesWarning": "Nota: Cuando se utilizan temas multidimensionales donde los valores dependen de tokens de otro tema, es posible que la conexión de estilos no funcione como se esperaba.", "attachLocalStyles": "Adjuntar estilos locales", + "attachAllVariables": "Adjuntar todas las variables", "selectAll": "Seleccionar todo", "detachSelected": "Separar seleccionado", "saveTheme": "Guardar tema", diff --git a/packages/tokens-studio-for-figma/src/i18n/lang/fr/tokens.json b/packages/tokens-studio-for-figma/src/i18n/lang/fr/tokens.json index b87e6f2dde..3d72115efb 100644 --- a/packages/tokens-studio-for-figma/src/i18n/lang/fr/tokens.json +++ b/packages/tokens-studio-for-figma/src/i18n/lang/fr/tokens.json @@ -223,6 +223,7 @@ "returnToOverview": "Retour à l'aperçu", "stylesVarMultiDimensionalThemesWarning": "Remarque : lors de l'utilisation de thèmes multidimensionnels dont les valeurs dépendent des jetons d'un autre thème, les styles de connexion peuvent ne pas fonctionner comme prévu.", "attachLocalStyles": "Attacher des styles locaux", + "attachAllVariables": "Attacher toutes les variables", "selectAll": "Tout sélectionner", "detachSelected": "Détacher la sélection", "saveTheme": "Enregistrer le thème", diff --git a/packages/tokens-studio-for-figma/src/i18n/lang/hi/tokens.json b/packages/tokens-studio-for-figma/src/i18n/lang/hi/tokens.json index a6c269f4a0..6c5d3a071a 100644 --- a/packages/tokens-studio-for-figma/src/i18n/lang/hi/tokens.json +++ b/packages/tokens-studio-for-figma/src/i18n/lang/hi/tokens.json @@ -223,6 +223,7 @@ "returnToOverview": "सिंहावलोकन पर लौटें", "stylesVarMultiDimensionalThemesWarning": "नोट: बहु-आयामी थीम का उपयोग करते समय जहां मान किसी अन्य थीम के टोकन पर निर्भर करते हैं, कनेक्टिंग शैलियाँ अपेक्षा के अनुरूप काम नहीं कर सकती हैं।", "attachLocalStyles": "स्थानीय शैलियाँ संलग्न करें", + "attachAllVariables": "सभी चर संलग्न करें", "selectAll": "सबका चयन करें", "detachSelected": "अलग करें चयनित", "saveTheme": "थीम सहेजें", diff --git a/packages/tokens-studio-for-figma/src/i18n/lang/nl/tokens.json b/packages/tokens-studio-for-figma/src/i18n/lang/nl/tokens.json index 99040b7ca9..0b10feb4d9 100644 --- a/packages/tokens-studio-for-figma/src/i18n/lang/nl/tokens.json +++ b/packages/tokens-studio-for-figma/src/i18n/lang/nl/tokens.json @@ -223,6 +223,7 @@ "returnToOverview": "Terug naar overzicht", "stylesVarMultiDimensionalThemesWarning": "Opmerking: bij het gebruik van multidimensionale thema's waarbij waarden afhankelijk zijn van tokens van een ander thema, werken verbindingsstijlen mogelijk niet zoals verwacht.", "attachLocalStyles": "Voeg lokale stijlen toe", + "attachAllVariables": "Voeg alle variabelen toe", "selectAll": "Selecteer alles", "detachSelected": "Geselecteerd loskoppelen", "saveTheme": "Thema opslaan", diff --git a/packages/tokens-studio-for-figma/src/i18n/lang/zh/tokens.json b/packages/tokens-studio-for-figma/src/i18n/lang/zh/tokens.json index 22a20afbcb..bb18b0104a 100644 --- a/packages/tokens-studio-for-figma/src/i18n/lang/zh/tokens.json +++ b/packages/tokens-studio-for-figma/src/i18n/lang/zh/tokens.json @@ -227,6 +227,7 @@ "returnToOverview": "返回概览", "stylesVarMultiDimensionalThemesWarning": "注意:当使用多维主题时,其中值取决于另一个主题的标记,连接样式可能无法按预期工作。", "attachLocalStyles": "附加本地样式", + "attachAllVariables": "附加所有变量", "selectAll": "全选", "detachSelected": "分离选定的", "saveTheme": "保存主题",