From 589d5d24586b9a39343bf818eef029c18e911e94 Mon Sep 17 00:00:00 2001 From: Mark Learst Date: Mon, 29 Dec 2025 19:30:51 -0500 Subject: [PATCH] feat(utils): add case-insensitive option to filterVariables Add caseInsensitive option to filterVariables() for flexible name matching, enabling case-agnostic search in variable filtering scenarios. Problem: - Users searching for 'color' wouldn't find 'Primary Color' - No way to perform case-insensitive variable name searches - Common UX pattern in search interfaces not supported Solution: - Added caseInsensitive boolean option to FilterVariablesCriteria - When true, both variable name and search term are lowercased - Defaults to false for backward compatibility Usage: import { filterVariables } from '@figma-vars/hooks'; // Case-insensitive search filterVariables(vars, { name: 'color', caseInsensitive: true }); // Matches: 'Primary Color', 'COLOR_BG', 'MyColor' Changes: - src/utils/filterVariables.ts: Added caseInsensitive option + interface - src/utils/index.ts: Export FilterVariablesCriteria type - tests/utils/filterVariables.test.ts: Added 6 tests for case-insensitive Test coverage: 100% Refs: Codex Audit Item #13 --- src/utils/filterVariables.ts | 40 +++++++++++++++++++--- src/utils/index.ts | 1 + tests/utils/filterVariables.test.ts | 53 +++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/src/utils/filterVariables.ts b/src/utils/filterVariables.ts index 1da0f9e..9e1c6e8 100644 --- a/src/utils/filterVariables.ts +++ b/src/utils/filterVariables.ts @@ -1,10 +1,31 @@ import type { FigmaVariable, ResolvedType } from 'types' +/** + * Criteria for filtering Figma variables. + * + * @public + */ +export interface FilterVariablesCriteria { + /** + * Filter by resolved variable type (e.g., 'COLOR', 'FLOAT', 'STRING', 'BOOLEAN'). + */ + resolvedType?: ResolvedType + /** + * Substring to match against variable names. + */ + name?: string + /** + * When true, name matching is case-insensitive. + * @defaultValue false + */ + caseInsensitive?: boolean +} + /** * Utility function to filter Figma variables by type and/or substring name match. * * @remarks - * Returns a new array of variables matching the provided criteria. Use for building type pickers, variable search, dashboard views, or bulk edit tools. Filtering is case-sensitive for `name`. Pass no criteria to return all variables unfiltered. + * Returns a new array of variables matching the provided criteria. Use for building type pickers, variable search, dashboard views, or bulk edit tools. By default, filtering is case-sensitive for `name`. Set `caseInsensitive: true` for case-insensitive matching. Pass no criteria to return all variables unfiltered. * * @param variables - The array of FigmaVariable objects to filter. * @param criteria - Object specifying filter fields. Provide a `resolvedType` (e.g., 'COLOR', 'FLOAT') and/or a `name` substring to match variable names. @@ -22,19 +43,30 @@ import type { FigmaVariable, ResolvedType } from 'types' * * // Example 3: Filter variables that are COLOR and include 'brand' in name * const filtered = filterVariables(allVars, { resolvedType: 'COLOR', name: 'brand' }); + * + * // Example 4: Case-insensitive name search + * const matches = filterVariables(allVars, { name: 'BRAND', caseInsensitive: true }); * ``` * * @public */ export function filterVariables( variables: FigmaVariable[], - criteria: { resolvedType?: ResolvedType; name?: string } + criteria: FilterVariablesCriteria ): FigmaVariable[] { return variables.filter(v => { let match = true - if (criteria.resolvedType) + if (criteria.resolvedType) { match = match && v.resolvedType === criteria.resolvedType - if (criteria.name) match = match && v.name.includes(criteria.name) + } + if (criteria.name) { + if (criteria.caseInsensitive) { + match = + match && v.name.toLowerCase().includes(criteria.name.toLowerCase()) + } else { + match = match && v.name.includes(criteria.name) + } + } return match }) } diff --git a/src/utils/index.ts b/src/utils/index.ts index e1c1d23..137df29 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -19,6 +19,7 @@ * @public */ export { filterVariables } from 'utils/filterVariables' +export type { FilterVariablesCriteria } from 'utils/filterVariables' export { isFigmaApiError, getErrorStatus, diff --git a/tests/utils/filterVariables.test.ts b/tests/utils/filterVariables.test.ts index 0733ec4..6f30f33 100644 --- a/tests/utils/filterVariables.test.ts +++ b/tests/utils/filterVariables.test.ts @@ -74,4 +74,57 @@ describe('filterVariables', () => { const result = filterVariables(mockVariables, { name: 'NonExistent' }) expect(result).toHaveLength(0) }) + + describe('caseInsensitive option', () => { + it('should match case-insensitively when caseInsensitive is true', () => { + const result = filterVariables(mockVariables, { + name: 'color', + caseInsensitive: true, + }) + expect(result).toHaveLength(2) + expect(result.map(v => v.name)).toEqual([ + 'Primary Color', + 'Secondary Color', + ]) + }) + + it('should match uppercase search against lowercase names', () => { + const result = filterVariables(mockVariables, { + name: 'COLOR', + caseInsensitive: true, + }) + expect(result).toHaveLength(2) + }) + + it('should match mixed case search', () => { + const result = filterVariables(mockVariables, { + name: 'CoLoR', + caseInsensitive: true, + }) + expect(result).toHaveLength(2) + }) + + it('should be case-sensitive by default', () => { + const result = filterVariables(mockVariables, { name: 'color' }) + expect(result).toHaveLength(0) // No match because 'Color' !== 'color' + }) + + it('should be case-sensitive when caseInsensitive is false', () => { + const result = filterVariables(mockVariables, { + name: 'color', + caseInsensitive: false, + }) + expect(result).toHaveLength(0) + }) + + it('should combine caseInsensitive with resolvedType filter', () => { + const result = filterVariables(mockVariables, { + resolvedType: 'COLOR', + name: 'primary', + caseInsensitive: true, + }) + expect(result).toHaveLength(1) + expect(result[0]!.name).toBe('Primary Color') + }) + }) })