Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions src/utils/filterVariables.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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())
Comment on lines +63 to +65
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The criteria.name.toLowerCase() is being called once per variable in the filter loop. Consider computing the lowercase search term once before the filter to improve performance, especially when filtering large arrays of variables. Store it in a constant like const lowerName = criteria.name.toLowerCase() before the filter operation and use that in the comparison.

Copilot uses AI. Check for mistakes.
} else {
match = match && v.name.includes(criteria.name)
}
}
return match
})
}
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* @public
*/
export { filterVariables } from 'utils/filterVariables'
export type { FilterVariablesCriteria } from 'utils/filterVariables'
export {
isFigmaApiError,
getErrorStatus,
Expand Down
53 changes: 53 additions & 0 deletions tests/utils/filterVariables.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
})