From d07a794e8e136d73ee0230ea4aa2a010728ccd88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20K=C3=B6rner?= Date: Fri, 19 Jun 2026 23:45:35 +0200 Subject: [PATCH] feat: author variant tags when exporting a style A style author can now assign descriptive tags to variants from a new Tags tab in each component group, the same way per-variant weights are set. Each tag is validated against the schema grammar (category or category:value in camelCase, up to 129 characters, at most 32 per variant, unique) and written as variant.tags in the exported definition, dropped when empty. DiceBear core and the language ports already read these tags to filter variants through the tags render option. This adds the authoring side so styles can carry the tags that filter acts on. --- src/code/export/createExportDefinition.ts | 8 +- src/code/export/prepareExport.ts | 2 + .../settings/getComponentGroupSettings.ts | 1 + src/code/types.ts | 2 + src/ui/components/TagsGroup.vue | 173 ++++++++++++++++++ .../components/forms/ComponentGroupForm.vue | 25 ++- src/ui/stores/plugin.ts | 2 +- src/ui/types.ts | 1 + src/ui/utils/sanitizeSettings.ts | 52 ++++++ 9 files changed, 260 insertions(+), 6 deletions(-) create mode 100644 src/ui/components/TagsGroup.vue diff --git a/src/code/export/createExportDefinition.ts b/src/code/export/createExportDefinition.ts index 6e4be3d..61aecf4 100644 --- a/src/code/export/createExportDefinition.ts +++ b/src/code/export/createExportDefinition.ts @@ -61,10 +61,12 @@ export async function createExportDefinition(exportData: Export) { baseEntry.height = roundTo(Math.max(baseEntry.height, componentNode.height), precision); const weight = componentGroupValue.settings.weights[componentKey] ?? 1; + const tags = componentGroupValue.settings.tags[componentKey] ?? []; baseEntry.variants[componentKey] = { elements: convertSvgsonToDefinition(await parse(componentContentWithSvg)).children ?? [], weight: weight === 1 ? undefined : weight, + tags: tags.length > 0 ? tags : undefined, }; } } @@ -111,13 +113,13 @@ export async function createExportDefinition(exportData: Export) { // Create definition const bodyContent = await createTemplateString( exportData, - (await figma.getNodeByIdAsync(exportData.frame.id)) as FrameNode, + (await figma.getNodeByIdAsync(exportData.frame.id)) as FrameNode ); const bodyContentWithSvg = `${bodyContent}`; return JSON.stringify( removeEmptyValuesFromObject({ - $schema: 'https://cdn.hopjs.net/npm/@dicebear/schema@1.1.0/dist/definition.min.json', + $schema: 'https://cdn.hopjs.net/npm/@dicebear/schema@1.3.0/dist/definition.min.json', $comment: 'This file was generated by the DiceBear Exporter for Figma. https://www.figma.com/community/plugin/1005765655729342787', meta: { @@ -148,6 +150,6 @@ export async function createExportDefinition(exportData: Export) { colors: colors, }), undefined, - 2, + 2 ); } diff --git a/src/code/export/prepareExport.ts b/src/code/export/prepareExport.ts index 61198c7..4aac129 100644 --- a/src/code/export/prepareExport.ts +++ b/src/code/export/prepareExport.ts @@ -203,6 +203,7 @@ function ensureMasterGroupRegistered( ...settings, defaults: {}, weights: {}, + tags: {}, }, collection: {}, width: 0, @@ -220,6 +221,7 @@ function ensureMasterGroupRegistered( componentGroup.settings.defaults[componentName] = settings.defaults[componentName] ?? true; componentGroup.settings.weights[componentName] = settings.weights[componentName] ?? 1; + componentGroup.settings.tags[componentName] = settings.tags[componentName] ?? []; pending.push(component); } diff --git a/src/code/settings/getComponentGroupSettings.ts b/src/code/settings/getComponentGroupSettings.ts index a1bb447..c5bdf0f 100644 --- a/src/code/settings/getComponentGroupSettings.ts +++ b/src/code/settings/getComponentGroupSettings.ts @@ -46,6 +46,7 @@ export function getComponentGroupSettings(frame: FrameNode, componentGroup: stri return { defaults: {}, weights: {}, + tags: {}, probability: null, rotation: null, scale: null, diff --git a/src/code/types.ts b/src/code/types.ts index 5d66477..143f2e9 100644 --- a/src/code/types.ts +++ b/src/code/types.ts @@ -25,6 +25,7 @@ export type RangeValue = DefinitionRange | null; export type ComponentGroupSettings = { defaults: Record; weights: Record; + tags: Record; probability: number | null; rotation: RangeValue; scale: RangeValue; @@ -115,6 +116,7 @@ export type DefinitionComponentBase = { { elements: DefinitionElement[]; weight?: number; + tags?: string[]; } >; }; diff --git a/src/ui/components/TagsGroup.vue b/src/ui/components/TagsGroup.vue new file mode 100644 index 0000000..0841694 --- /dev/null +++ b/src/ui/components/TagsGroup.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/src/ui/components/forms/ComponentGroupForm.vue b/src/ui/components/forms/ComponentGroupForm.vue index 7e8ddb7..d8927ec 100644 --- a/src/ui/components/forms/ComponentGroupForm.vue +++ b/src/ui/components/forms/ComponentGroupForm.vue @@ -9,6 +9,7 @@ import FieldReset from '../FieldReset.vue'; import RangeField from '../RangeField.vue'; import ToggleGroup from '../ToggleGroup.vue'; import WeightGroup from '../WeightGroup.vue'; +import TagsGroup from '../TagsGroup.vue'; const props = defineProps<{ componentGroup: string }>(); @@ -67,15 +68,17 @@ function resetWeights(): void { } } +const tagsKeys = computed(() => Object.keys(settings.value.tags)); + const tab = computed({ get: () => { - if (store.componentTab === 'weights' && !isDefinition.value) { + if ((store.componentTab === 'weights' || store.componentTab === 'tags') && !isDefinition.value) { return 'settings'; } return store.componentTab; }, - set: (next: 'settings' | 'weights' | 'normalize') => { + set: (next: 'settings' | 'weights' | 'tags' | 'normalize') => { store.componentTab = next; }, }); @@ -174,6 +177,9 @@ function onRetry() { > Weights + @@ -257,6 +263,21 @@ function onRetry() { + +