Skip to content

Commit f82fc34

Browse files
refactor: update naming for brand color (#1785)
* refactor: update naming for brand color * change location of test file * reset color if errors
1 parent 3c70f5e commit f82fc34

File tree

4 files changed

+128
-14
lines changed

4 files changed

+128
-14
lines changed

apps/studio/src/lib/editor/engine/theme/index.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { ColorItem } from '@/routes/editor/LayersPanel/BrandTab/ColorPanel/
44
import { DEFAULT_COLOR_NAME, MainChannels } from '@onlook/models';
55
import type { ConfigResult, ParsedColors, ThemeColors } from '@onlook/models/assets';
66
import { Theme } from '@onlook/models/assets';
7-
import { Color } from '@onlook/utility';
7+
import { Color, generateUniqueName } from '@onlook/utility';
88
import { makeAutoObservable } from 'mobx';
99
import colors from 'tailwindcss/colors';
1010
import type { EditorEngine } from '..';
@@ -30,9 +30,17 @@ export class ThemeManager {
3030
this.scanConfig();
3131
}
3232

33+
private reset() {
34+
this.defaultColors = {};
35+
this.brandColors = {};
36+
this.configPath = null;
37+
this.cssPath = null;
38+
}
39+
3340
async scanConfig() {
3441
const projectRoot = this.projectsManager.project?.folderPath;
3542
if (!projectRoot) {
43+
this.reset();
3644
return;
3745
}
3846

@@ -42,6 +50,7 @@ export class ThemeManager {
4250
})) as ConfigResult;
4351

4452
if (!configResult) {
53+
this.reset();
4554
return;
4655
}
4756

@@ -204,6 +213,7 @@ export class ThemeManager {
204213
}
205214
this.brandColors = colorGroupsObj;
206215
} catch (error) {
216+
this.reset();
207217
console.error('Error loading colors:', error);
208218
}
209219
}
@@ -451,12 +461,19 @@ export class ThemeManager {
451461
if (!colorToDuplicate) {
452462
throw new Error('Color not found');
453463
}
454-
// If the color name is a number, we need to add a suffix to the new color name
455-
const randomId = customAlphabet('0123456789', 5)();
456-
const randomText = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5)();
457-
const newName = isNaN(Number(colorName))
458-
? `${colorName}Copy${randomText}`
459-
: `${colorName}${randomId}`;
464+
465+
// Generate a unique name for the duplicated color
466+
const existingNames = group.map((color) => color.name);
467+
let newName: string;
468+
469+
if (isNaN(Number(colorName))) {
470+
// For non-numeric names, use the generateUniqueName utility
471+
newName = generateUniqueName(colorName, existingNames);
472+
} else {
473+
// For numeric names, generate a random numeric suffix
474+
const randomId = customAlphabet('0123456789', 5)();
475+
newName = `${colorName}${randomId}`;
476+
}
460477

461478
const color = Color.from(
462479
theme === Theme.DARK

apps/studio/src/routes/editor/LayersPanel/BrandTab/ColorPanel/ColorPalletGroup.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@ import {
1111
} from '@onlook/ui/dropdown-menu';
1212
import { Icons } from '@onlook/ui/icons';
1313
import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from '@onlook/ui/tooltip';
14-
import { Color, toNormalCase } from '@onlook/utility';
14+
import { Color, toNormalCase, generateUniqueName } from '@onlook/utility';
1515
import { useState } from 'react';
1616
import { ColorPopover } from './ColorPopover';
1717
import { ColorNameInput } from './ColorNameInput';
18-
import { customAlphabet } from 'nanoid/non-secure';
19-
2018
export interface ColorItem {
2119
name: string;
2220
originalKey: string;
@@ -125,10 +123,7 @@ export const BrandPalletGroup = ({
125123
};
126124

127125
const generateUniqueColorName = () => {
128-
const randomIdText = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5)();
129-
const randomIdNumber = customAlphabet('0123456789', 5)();
130-
const randomId = isNaN(Number(title)) ? randomIdText : randomIdNumber;
131-
return `${title} ${randomId}`;
126+
return generateUniqueName(title, existedName);
132127
};
133128

134129
return (

packages/utility/src/string.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { camelCase } from 'lodash';
2+
13
export function isEmptyString(str: string): boolean {
24
return str.trim() === '';
35
}
@@ -32,3 +34,29 @@ export function toNormalCase(str: string): string {
3234
export function capitalizeFirstLetter(string: string) {
3335
return string.charAt(0).toUpperCase() + string.slice(1);
3436
}
37+
38+
/**
39+
* Generates a unique name by appending a number to the base name until it doesn't conflict with existing names.
40+
* The comparison is done using camelCase to ensure consistent name formatting.
41+
* @param baseName The base name to start with
42+
* @param existingNames Array of existing names to check against
43+
* @param transformFn Optional function to transform the name before comparison (defaults to camelCase)
44+
* @returns A unique name that doesn't conflict with existing names
45+
*/
46+
export function generateUniqueName(
47+
baseName: string,
48+
existingNames: string[],
49+
transformFn: (str: string) => string = camelCase,
50+
): string {
51+
let counter = 1;
52+
let newName = `${baseName} ${counter}`;
53+
let transformedName = transformFn(newName);
54+
55+
while (existingNames.includes(transformedName)) {
56+
counter++;
57+
newName = `${baseName} ${counter}`;
58+
transformedName = transformFn(newName);
59+
}
60+
61+
return newName;
62+
}

packages/utility/test/string.test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { generateUniqueName } from '@onlook/utility';
2+
3+
describe('generateUniqueName', () => {
4+
it('should generate a unique name when no existing names', () => {
5+
const baseName = 'Primary';
6+
const existingNames: string[] = [];
7+
const result = generateUniqueName(baseName, existingNames);
8+
expect(result).toBe('Primary 1');
9+
});
10+
11+
it('should generate a unique name when name exists', () => {
12+
const baseName = 'Primary';
13+
const existingNames = ['primary'];
14+
const result = generateUniqueName(baseName, existingNames);
15+
expect(result).toBe('Primary 1');
16+
});
17+
18+
it('should increment counter when multiple names exist', () => {
19+
const baseName = 'Primary';
20+
const existingNames = ['primary', 'primary1', 'primary2'];
21+
const result = generateUniqueName(baseName, existingNames);
22+
expect(result).toBe('Primary 3');
23+
});
24+
25+
it('should handle names with spaces', () => {
26+
const baseName = 'Primary Color';
27+
const existingNames = ['primaryColor'];
28+
const result = generateUniqueName(baseName, existingNames);
29+
expect(result).toBe('Primary Color 1');
30+
});
31+
32+
it('should handle special characters in base name', () => {
33+
const baseName = 'Primary-Color';
34+
const existingNames = ['primaryColor'];
35+
const result = generateUniqueName(baseName, existingNames);
36+
expect(result).toBe('Primary-Color 1');
37+
});
38+
39+
it('should handle empty base name', () => {
40+
const baseName = '';
41+
const existingNames: string[] = [];
42+
const result = generateUniqueName(baseName, existingNames);
43+
expect(result).toBe(' 1');
44+
});
45+
46+
it('should work with custom transform function', () => {
47+
const baseName = 'Primary';
48+
const existingNames = ['PRIMARY'];
49+
const customTransform = (str: string) => str.toUpperCase();
50+
const result = generateUniqueName(baseName, existingNames, customTransform);
51+
expect(result).toBe('Primary 1');
52+
});
53+
54+
it('should handle numeric base names', () => {
55+
const baseName = '100';
56+
const existingNames = ['100'];
57+
const result = generateUniqueName(baseName, existingNames);
58+
expect(result).toBe('100 1');
59+
});
60+
61+
it('should handle mixed case existing names', () => {
62+
const baseName = 'Primary';
63+
const existingNames = ['PRIMARY', 'primary', 'Primary'];
64+
const result = generateUniqueName(baseName, existingNames);
65+
expect(result).toBe('Primary 1');
66+
});
67+
68+
it('should handle consecutive numbers in existing names', () => {
69+
const baseName = 'Primary';
70+
const existingNames = ['primary', 'primary1', 'primary3', 'primary4'];
71+
const result = generateUniqueName(baseName, existingNames);
72+
expect(result).toBe('Primary 2');
73+
});
74+
});

0 commit comments

Comments
 (0)